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)

OP OpenVibers committed 17 days ago · 76d9f902e6 · 2 files, +16 −5

Changes

ddiff --git a/server/rooms/ai-tools.js b/server/rooms/ai-tools.js
iindex 1844eee..e2e5f2d 100644
---- a/server/rooms/ai-tools.js
++++ b/server/rooms/ai-tools.js
@@@ -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) {
ddiff --git a/test/room-ai-tools.test.js b/test/room-ai-tools.test.js
iindex af3d6f1..e8ce676 100644
---- a/test/room-ai-tools.test.js
++++ b/test/room-ai-tools.test.js
@@@ -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('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 () => {