openvibers/openvibe.chat Commits
AI members (Vibe) search OpenVibe through the Search Fabric's network scope (/api/v1/query), the old search on a Search without it (#235)
Changes
@@@ -4,7 +4,8 @@* turns of OpenVibe.AI's `chat.member.turn` (server/rooms/ai-members.js). Each is small, read-only and bounded: an AI* member still only writes in its room.*-- * search_openvibe OpenVibe.Search's public results for a query (news, deals, coupons, wiki, listings, videos…)++ * search_openvibe OpenVibe.Search's public results for a query (news, deals, coupons, wiki, listings, videos…), through++ * the Search Fabric's network scope (GET /api/v1/query); /api/v1/search on a Search without it* read_page a web page's title, text and links, through OpenVibe.Tools' `read` tool (Tools decides what it may* reach: public http(s) addresses only); Chat never fetches the address itself* web_search Vibe only (a Vibe chat, or Vibe added to a room): the open web, answered by OpenVibe.Actor's web@@@ -96,12 +97,16 @@ async function searchOpenvibe(args, { fetchImpl = globalThis.fetch } = {}) {const ctrl = new AbortController();const timer = setTimeout(() => ctrl.abort(), 8000);try {-- const res = await fetchImpl(`${config.ai.searchUrl}/api/v1/search?${new URLSearchParams({ q: query, limit: String(limit) })}`, { headers: { accept: 'application/json', 'user-agent': 'OpenVibe.Chat AI member' }, signal: ctrl.signal, redirect: 'error' });++ const get = (path) => fetchImpl(`${config.ai.searchUrl}${path}`, { headers: { accept: 'application/json', 'user-agent': 'OpenVibe.Chat AI member' }, signal: ctrl.signal, redirect: 'error' });++ let res = await get(`/api/v1/query?${new URLSearchParams({ q: query, scopes: 'network', limit: String(limit) })}`);++ const fabric = res.status !== 404;++ if (!fabric) res = await get(`/api/v1/search?${new URLSearchParams({ q: query, limit: String(limit) })}`);if (!res.ok) return { error: `OpenVibe.Search answered ${res.status}` };const body = await res.json();const results = (Array.isArray(body.results) ? body.results : []).slice(0, limit).map((r) => ({title: String(r.title || '').slice(0, 200), site: r.owner, type: r.type,-- summary: stripTags(r.snippet_html || r.summary || '').slice(0, 300), url: r.canonical_url, published_at: r.published_at || null,++ summary: stripTags(fabric ? r.snippet : (r.snippet_html || r.summary || '')).slice(0, 300),++ url: fabric ? r.url : r.canonical_url, published_at: r.published_at || null,}));return { query, results, note: results.length ? 'Cite the url when you use a result.' : 'Nothing on OpenVibe matched.' };} catch (err) {@@@ -84,10 +84,16 @@ t('the tool list and the runners: bounded, fair, refusing what is not theirs', aassert.match(await aiTools.run('rm_rf', '{}'), /no tool called/);assert.match(await aiTools.run('roll_dice', '{not json'), /not valid JSON/);const seen = [];-- const fake = async (url) => { seen.push(String(url)); return new Response(JSON.stringify({ results: [{ owner: 'deals', type: 'offer', title: 'Air mattress for $67', snippet_html: 'An <mark>air</mark> mattress', canonical_url: 'https://openvibe.deals/d/x' }] }), { status: 200, headers: { 'content-type': 'application/json' } }); };++ const fake = async (url) => { seen.push(String(url)); return new Response(JSON.stringify({ results: [{ scope: 'network', kind: 'document', owner: 'deals', type: 'offer', title: 'Air mattress for $67', snippet: 'An air mattress', url: 'https://openvibe.deals/d/x' }] }), { status: 200, headers: { 'content-type': 'application/json' } }); };const found = await aiTools._runners.search_openvibe({ query: 'air mattress', limit: 99 }, { fetchImpl: fake });-- assert.ok(seen[0].startsWith(`${config.ai.searchUrl}/api/v1/search?`) && seen[0].includes('limit=8'));++ assert.ok(seen[0].startsWith(`${config.ai.searchUrl}/api/v1/query?`) && seen[0].includes('scopes=network') && seen[0].includes('limit=8'), 'the Search Fabric\'s network scope');assert.deepStrictEqual([found.results[0].site, found.results[0].summary, found.results[0].url], ['deals', 'An air mattress', 'https://openvibe.deals/d/x']);++ // A Search without the fabric answers 404 there: the old search answers instead.++ const seenOld = [];++ const old = async (url) => { seenOld.push(String(url)); if (String(url).includes('/api/v1/query')) return new Response('{}', { status: 404 }); return new Response(JSON.stringify({ results: [{ owner: 'deals', type: 'offer', title: 'Air mattress for $67', snippet_html: 'An <mark>air</mark> mattress', canonical_url: 'https://openvibe.deals/d/x' }] }), { status: 200, headers: { 'content-type': 'application/json' } }); };++ const foundOld = await aiTools._runners.search_openvibe({ query: 'air mattress' }, { fetchImpl: old });++ assert.ok(seenOld[1].startsWith(`${config.ai.searchUrl}/api/v1/search?`));++ assert.deepStrictEqual([foundOld.results[0].summary, foundOld.results[0].url], ['An air mattress', 'https://openvibe.deals/d/x']);});t('@dealer rolls through a tool: the call runs here, its result goes back, the text answer is posted', async () => {
@@ -4,7 +4,8 @@
@@ -4,7 +4,8 @@
* turns of OpenVibe.AI's `chat.member.turn` (server/rooms/ai-members.js). Each is small, read-only and bounded: an AI
* turns of OpenVibe.AI's `chat.member.turn` (server/rooms/ai-members.js). Each is small, read-only and bounded: an AI
* member still only writes in its room.
* member still only writes in its room.
*
*
- * search_openvibe OpenVibe.Search's public results for a query (news, deals, coupons, wiki, listings, videos…)
+ * search_openvibe OpenVibe.Search's public results for a query (news, deals, coupons, wiki, listings, videos…), through
+ * the Search Fabric's network scope (GET /api/v1/query); /api/v1/search on a Search without it
* read_page a web page's title, text and links, through OpenVibe.Tools' `read` tool (Tools decides what it may
* read_page a web page's title, text and links, through OpenVibe.Tools' `read` tool (Tools decides what it may
* reach: public http(s) addresses only); Chat never fetches the address itself
* reach: public http(s) addresses only); Chat never fetches the address itself
* web_search Vibe only (a Vibe chat, or Vibe added to a room): the open web, answered by OpenVibe.Actor's web
* web_search Vibe only (a Vibe chat, or Vibe added to a room): the open web, answered by OpenVibe.Actor's web
@@ -96,12 +97,16 @@ async function searchOpenvibe(args, { fetchImpl = globalThis.fetch } = {}) {
@@ -96,12 +97,16 @@ async function searchOpenvibe(args, { fetchImpl = globalThis.fetch } = {}) {
const ctrl = new AbortController();
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 8000);
const timer = setTimeout(() => ctrl.abort(), 8000);
try {
try {
- const res = await fetchImpl(`${config.ai.searchUrl}/api/v1/search?${new URLSearchParams({ q: query, limit: String(limit) })}`, { headers: { accept: 'application/json', 'user-agent': 'OpenVibe.Chat AI member' }, signal: ctrl.signal, redirect: 'error' });
+ const get = (path) => fetchImpl(`${config.ai.searchUrl}${path}`, { headers: { accept: 'application/json', 'user-agent': 'OpenVibe.Chat AI member' }, signal: ctrl.signal, redirect: 'error' });
+ let res = await get(`/api/v1/query?${new URLSearchParams({ q: query, scopes: 'network', limit: String(limit) })}`);
+ const fabric = res.status !== 404;
+ if (!fabric) res = await get(`/api/v1/search?${new URLSearchParams({ q: query, limit: String(limit) })}`);
if (!res.ok) return { error: `OpenVibe.Search answered ${res.status}` };
if (!res.ok) return { error: `OpenVibe.Search answered ${res.status}` };
const body = await res.json();
const body = await res.json();
const results = (Array.isArray(body.results) ? body.results : []).slice(0, limit).map((r) => ({
const results = (Array.isArray(body.results) ? body.results : []).slice(0, limit).map((r) => ({
title: String(r.title || '').slice(0, 200), site: r.owner, type: r.type,
title: String(r.title || '').slice(0, 200), site: r.owner, type: r.type,
- summary: stripTags(r.snippet_html || r.summary || '').slice(0, 300), url: r.canonical_url, published_at: r.published_at || null,
+ summary: stripTags(fabric ? r.snippet : (r.snippet_html || r.summary || '')).slice(0, 300),
+ url: fabric ? r.url : r.canonical_url, published_at: r.published_at || null,
}));
}));
return { query, results, note: results.length ? 'Cite the url when you use a result.' : 'Nothing on OpenVibe matched.' };
return { query, results, note: results.length ? 'Cite the url when you use a result.' : 'Nothing on OpenVibe matched.' };
} catch (err) {
} catch (err) {
@@ -84,10 +84,16 @@ t('the tool list and the runners: bounded, fair, refusing what is not theirs', a
@@ -84,10 +84,16 @@ t('the tool list and the runners: bounded, fair, refusing what is not theirs', a
assert.match(await aiTools.run('rm_rf', '{}'), /no tool called/);
assert.match(await aiTools.run('rm_rf', '{}'), /no tool called/);
assert.match(await aiTools.run('roll_dice', '{not json'), /not valid JSON/);
assert.match(await aiTools.run('roll_dice', '{not json'), /not valid JSON/);
const seen = [];
const seen = [];
- const fake = async (url) => { seen.push(String(url)); return new Response(JSON.stringify({ results: [{ owner: 'deals', type: 'offer', title: 'Air mattress for $67', snippet_html: 'An <mark>air</mark> mattress', canonical_url: 'https://openvibe.deals/d/x' }] }), { status: 200, headers: { 'content-type': 'application/json' } }); };
+ const fake = async (url) => { seen.push(String(url)); return new Response(JSON.stringify({ results: [{ scope: 'network', kind: 'document', owner: 'deals', type: 'offer', title: 'Air mattress for $67', snippet: 'An air mattress', url: 'https://openvibe.deals/d/x' }] }), { status: 200, headers: { 'content-type': 'application/json' } }); };
const found = await aiTools._runners.search_openvibe({ query: 'air mattress', limit: 99 }, { fetchImpl: fake });
const found = await aiTools._runners.search_openvibe({ query: 'air mattress', limit: 99 }, { fetchImpl: fake });
- assert.ok(seen[0].startsWith(`${config.ai.searchUrl}/api/v1/search?`) && seen[0].includes('limit=8'));
+ assert.ok(seen[0].startsWith(`${config.ai.searchUrl}/api/v1/query?`) && seen[0].includes('scopes=network') && seen[0].includes('limit=8'), 'the Search Fabric\'s network scope');
assert.deepStrictEqual([found.results[0].site, found.results[0].summary, found.results[0].url], ['deals', 'An air mattress', 'https://openvibe.deals/d/x']);
assert.deepStrictEqual([found.results[0].site, found.results[0].summary, found.results[0].url], ['deals', 'An air mattress', 'https://openvibe.deals/d/x']);
+ // A Search without the fabric answers 404 there: the old search answers instead.
+ const seenOld = [];
+ const old = async (url) => { seenOld.push(String(url)); if (String(url).includes('/api/v1/query')) return new Response('{}', { status: 404 }); return new Response(JSON.stringify({ results: [{ owner: 'deals', type: 'offer', title: 'Air mattress for $67', snippet_html: 'An <mark>air</mark> mattress', canonical_url: 'https://openvibe.deals/d/x' }] }), { status: 200, headers: { 'content-type': 'application/json' } }); };
+ const foundOld = await aiTools._runners.search_openvibe({ query: 'air mattress' }, { fetchImpl: old });
+ assert.ok(seenOld[1].startsWith(`${config.ai.searchUrl}/api/v1/search?`));
+ assert.deepStrictEqual([foundOld.results[0].summary, foundOld.results[0].url], ['An air mattress', 'https://openvibe.deals/d/x']);
});
});
t('@dealer rolls through a tool: the call runs here, its result goes back, the text answer is posted', async () => {
t('@dealer rolls through a tool: the call runs here, its result goes back, the text answer is posted', async () => {