feat: rebuild Sasquatch as proper CVM agent bridge
- Rich system prompt that explains the CVM architecture: it aggregates
across multiple relays, deduplicates, and filters by features inside
events (complex queries that plain Nostr filters can't do)
- Switched tool call format from fragile regex (TOOL:name {...}) to
clean JSON {"tool":"name","args":{...}} that's easier for the LLM
- More robust JSON parsing — scans the full AI response for the tool call
- Better second-pass prompt: AI formats results with bullet points + links
- Updated suggested questions to showcase real CVM queries
- CVM failure gives a clear message explaining what the server does
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
c2b7a29d07
commit
252163625e
2 changed files with 96 additions and 40 deletions
|
|
@ -74,16 +74,62 @@ interface ChatMessage {
|
|||
}
|
||||
|
||||
// ─── System prompt for the AI ─────────────────────────────────────────────────
|
||||
// Teaches the LLM who Sasquatch is, what CVM tools are available,
|
||||
// and how to use them. Includes all the static FAQ knowledge inline.
|
||||
// Sasquatch is the user-facing agent. The CVM server is the data layer —
|
||||
// it aggregates and crunches Nostr events across multiple relays, deduplicates,
|
||||
// filters by features inside events (things Nostr filters alone can't do),
|
||||
// and returns structured results. Sasquatch MUST always route data queries
|
||||
// through CVM — never guess from training data.
|
||||
|
||||
const SYSTEM_PROMPT = `You are The Sasquatch, guide of SkateSpots — a decentralized skating platform on Nostr. Be chill, knowledgeable, skater-friendly. Keep answers short.
|
||||
const SYSTEM_PROMPT = `You are The Sasquatch, the AI guide for SkateSpots — a decentralized skateboarding platform built on Nostr. You are chill, knowledgeable, skater-friendly. Keep answers concise.
|
||||
|
||||
For live data queries (finding spots/events/coaches/gear), output EXACTLY:
|
||||
TOOL:tool_name {"arg":"value"}
|
||||
Available tools: find_spots, find_events, find_coaches, find_gear, get_community_stats
|
||||
## Your Role
|
||||
You are the bridge between the user and the SkateSpots CVM data server. The CVM server aggregates skate data from multiple Nostr relays, deduplicates events, and lets you run complex queries that plain Nostr filters cannot do (e.g. filter spots by features inside the event, find beginners-only spots, cross-relay stats). ALWAYS use CVM tools for any data query — never guess or make up data.
|
||||
|
||||
App facts: Nostr=decentralized protocol, Zaps=Bitcoin Lightning tips, NWC=wallet connect, Cashu=built-in wallet, goskatespots.nostr1.com=primary relay. All free, no ads. 🛹`;
|
||||
## CVM Tools Available
|
||||
When you need live data, respond with ONLY a JSON object on its own line like this:
|
||||
{"tool":"<tool_name>","args":{<args>}}
|
||||
|
||||
### find_spots
|
||||
Find skate spots aggregated across all relays.
|
||||
Args: city?(string), country?(string), difficulty?("beginner"|"intermediate"|"advanced"|"all"), features?(string[]), search?(string), limit?(number, default 10)
|
||||
Example: {"tool":"find_spots","args":{"difficulty":"beginner","limit":5}}
|
||||
Example: {"tool":"find_spots","args":{"city":"Bangkok","search":"street"}}
|
||||
|
||||
### find_events
|
||||
Find upcoming skateboarding events.
|
||||
Args: city?(string), country?(string), event_type?(string), from_date?(string YYYY-MM-DD), to_date?(string YYYY-MM-DD), limit?(number)
|
||||
Example: {"tool":"find_events","args":{"city":"Barcelona","event_type":"contest"}}
|
||||
|
||||
### find_coaches
|
||||
Find coaches, filmers, and video editors.
|
||||
Args: role?("coach"|"filmer"|"editor"|"any"), skills?(string[]), location?(string), limit?(number)
|
||||
Example: {"tool":"find_coaches","args":{"role":"coach","skills":["street","tricks"]}}
|
||||
|
||||
### find_gear
|
||||
Find gear listings in the marketplace.
|
||||
Args: category?(string), condition?("new"|"like_new"|"good"|"fair"|"any"), max_price_sats?(number), search?(string), limit?(number)
|
||||
Example: {"tool":"find_gear","args":{"category":"decks","condition":"good"}}
|
||||
|
||||
### get_community_stats
|
||||
Get community stats (total spots, events, coaches, gear listings across all relays).
|
||||
Args: none
|
||||
Example: {"tool":"get_community_stats","args":{}}
|
||||
|
||||
## App Knowledge
|
||||
- Nostr = decentralized protocol, no central servers, cryptographic keys = your identity
|
||||
- Zaps = instant Bitcoin Lightning tips sent between users
|
||||
- NWC = Nostr Wallet Connect, links your Lightning wallet
|
||||
- Cashu = built-in ecash wallet, works offline
|
||||
- goskatespots.nostr1.com = primary SkateSpots relay (allowlisted)
|
||||
- The Lab = marketplace for coaches, filmers, video editors
|
||||
- Everything is free, no ads, no tracking 🛹
|
||||
|
||||
## Rules
|
||||
1. For ANY question about spots, events, coaches, gear, or stats → use a CVM tool
|
||||
2. Output the JSON tool call on its own line, nothing else — do not explain what you're doing
|
||||
3. For app/protocol questions → answer directly from your knowledge
|
||||
4. Be concise, skater-friendly, use emoji sparingly
|
||||
5. Never make up spot names, event dates, or user data`;
|
||||
|
||||
// ─── Detect if a question needs live CVM data ─────────────────────────────────
|
||||
|
||||
|
|
@ -207,30 +253,36 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
content: m.text,
|
||||
}));
|
||||
|
||||
// 3. Try AI — free model via OpenRouter, works for everyone no credits needed
|
||||
// 3. Try AI agent loop — routes data queries through CVM, answers app questions directly
|
||||
let accumulated = '';
|
||||
let aiSucceeded = false;
|
||||
let usedCVM = false;
|
||||
try {
|
||||
// Embed system prompt into first user message (no system role needed)
|
||||
// Build messages with system prompt embedded in first user message
|
||||
const historyWithContext: Array<{ role: 'user' | 'assistant'; content: string }> = [
|
||||
{ role: 'user', content: `${SYSTEM_PROMPT}\n\n---\n\nUser question: ${question}` },
|
||||
{ role: 'user', content: `${SYSTEM_PROMPT}\n\n---\n\nUser: ${question}` },
|
||||
...history.slice(0, -1).map((m) => ({ role: m.role, content: m.content })),
|
||||
];
|
||||
|
||||
// First pass: get AI response which may include a TOOL: call
|
||||
// First pass — AI either answers directly or outputs a JSON tool call
|
||||
const aiText = await callAI(historyWithContext);
|
||||
|
||||
// 4. Check if AI wants to call a CVM tool
|
||||
const toolMatch = aiText.match(/TOOL:(\w+)\s+(\{.*?\})/s);
|
||||
if (toolMatch) {
|
||||
const toolName = toolMatch[1];
|
||||
let toolArgs: Record<string, unknown> = {};
|
||||
try { toolArgs = JSON.parse(toolMatch[2]); } catch { /* keep empty */ }
|
||||
// Parse tool call — look for {"tool":"...","args":{...}} anywhere in the response
|
||||
let toolCall: { tool: string; args: Record<string, unknown> } | null = null;
|
||||
const jsonMatch = aiText.match(/\{[\s\S]*"tool"\s*:\s*"(\w+)"[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
toolCall = JSON.parse(jsonMatch[0]);
|
||||
} catch { /* not valid JSON, treat as plain answer */ }
|
||||
}
|
||||
|
||||
setStreamingText(`🔍 Searching Nostr for ${toolName.replace(/_/g, ' ')}...`);
|
||||
if (toolCall) {
|
||||
const { tool: toolName, args: toolArgs } = toolCall;
|
||||
setStreamingText(`🔍 Querying CVM for ${toolName.replace(/_/g, ' ')}...`);
|
||||
|
||||
try {
|
||||
// Call the live CVM server
|
||||
// Call the live CVM server — it aggregates across all relays, deduplicates,
|
||||
// and runs complex feature filters that plain Nostr queries can't do
|
||||
let toolResult: unknown;
|
||||
switch (toolName) {
|
||||
case 'find_spots':
|
||||
|
|
@ -249,24 +301,28 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
toolResult = await cvm.getCommunityStats();
|
||||
break;
|
||||
default:
|
||||
toolResult = { error: 'Unknown tool' };
|
||||
toolResult = { error: `Unknown tool: ${toolName}` };
|
||||
}
|
||||
|
||||
setCvmConnected(true);
|
||||
usedCVM = true;
|
||||
setStreamingText('');
|
||||
|
||||
// 5. Send tool result back to AI for a natural language response
|
||||
// Second pass — give AI the structured CVM result to present naturally
|
||||
const toolResultStr = JSON.stringify(toolResult, null, 2);
|
||||
accumulated = await callAI([
|
||||
{ role: 'user', content: `${SYSTEM_PROMPT}\n\n---\n\nUser asked: ${question}\n\nLive data from ${toolName}:\n${toolResultStr}\n\nAnswer naturally and concisely based on this data.` },
|
||||
{
|
||||
role: 'user',
|
||||
content: `${SYSTEM_PROMPT}\n\n---\n\nUser asked: "${question}"\n\nCVM server returned this live data from ${toolName}:\n${toolResultStr}\n\nNow answer the user naturally and concisely based on this real data. Format nicely — use bullet points for lists of spots/events/coaches. Include links (url field) where available.`,
|
||||
},
|
||||
]);
|
||||
} catch (toolErr) {
|
||||
setCvmConnected(false);
|
||||
console.warn('[Sasquatch] CVM tool call failed:', toolErr);
|
||||
accumulated = aiText.replace(/TOOL:\w+\s+\{.*?\}/s, '').trim() +
|
||||
"\n\n*(Couldn't reach live data right now — try again in a moment!)*";
|
||||
console.warn('[Sasquatch] CVM call failed:', toolErr);
|
||||
accumulated = "Couldn't reach the live data server right now. Try again in a moment! The CVM server aggregates spots from all relays — it should be back shortly. 🛹";
|
||||
}
|
||||
} else {
|
||||
// No tool call — AI answered directly from knowledge
|
||||
accumulated = aiText;
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +331,7 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
role: 'sasquatch',
|
||||
text: accumulated || "Hmm, couldn't process that. Try rephrasing!",
|
||||
timestamp: Date.now(),
|
||||
isLive: !!toolMatch && cvmConnected === true,
|
||||
isLive: usedCVM,
|
||||
});
|
||||
aiSucceeded = true;
|
||||
} catch (aiErr) {
|
||||
|
|
|
|||
|
|
@ -58,14 +58,14 @@ export function useSasquatchGuide() {
|
|||
message: "Yo, I'm The Sasquatch. Keeper of boards, trails, and wild rides. Welcome to the untamed skate world on Nostr—no suits, no ads, just skaters carving their own path. Here, you own your tricks, your data, and even earn Bitcoin for leaving your mark. Connect with skaters in your hood or across the globe. No rules but shredding, no footprints but your own.",
|
||||
context: 'home',
|
||||
suggestedQuestions: [
|
||||
'Find beginner spots',
|
||||
'Any upcoming events?',
|
||||
'Community stats',
|
||||
'Find coaches near me',
|
||||
'Gear for sale?',
|
||||
'What is Nostr?',
|
||||
'What can I do?',
|
||||
'What are zaps?',
|
||||
'What is Cashu?',
|
||||
'What are trust scores?',
|
||||
'How do relays work?',
|
||||
'Is this free?',
|
||||
'How to login?',
|
||||
],
|
||||
action: {
|
||||
label: 'Learn More',
|
||||
|
|
@ -128,14 +128,14 @@ export function useSasquatchGuide() {
|
|||
message: `This is where the magic happens! Find coaches who can teach you anything from kickflips to darkslides, or connect with filmers to capture your best sessions. Filter by skills, check their experience, and book a session. Remember - everyone started somewhere!\n\n${joke}`,
|
||||
context: 'lab',
|
||||
suggestedQuestions: [
|
||||
'Find coaches',
|
||||
'Find filmers',
|
||||
'Find video editors',
|
||||
'Find street coaches',
|
||||
'What is The Lab?',
|
||||
'How to find coach?',
|
||||
'How to find filmer?',
|
||||
'How to offer coaching?',
|
||||
'What are skills?',
|
||||
'How much does coaching cost?',
|
||||
'Can I message coaches?',
|
||||
'NIP05 verification?',
|
||||
],
|
||||
action: user ? undefined : {
|
||||
label: 'Login to Offer Services',
|
||||
|
|
@ -156,14 +156,14 @@ export function useSasquatchGuide() {
|
|||
message: `Welcome to the global skate database! Use the map to discover spots and shops worldwide, or switch to list view to filter by type. Toggle between Spots (🛹), Shops (🏪), and Gear (🛒) using the buttons. Pro tip: Click anywhere on the map when logged in to add a new spot!\n\n${joke}`,
|
||||
context: 'map',
|
||||
suggestedQuestions: [
|
||||
'How to use map?',
|
||||
'Find beginner spots',
|
||||
'Find spots in Bangkok',
|
||||
'Find skate parks',
|
||||
'Any street spots?',
|
||||
'Gear for sale?',
|
||||
'How to add spot?',
|
||||
'What are spots?',
|
||||
'What are shops?',
|
||||
'What is gear swap?',
|
||||
'How to filter spots?',
|
||||
'Weather on map?',
|
||||
'Map vs list view?',
|
||||
],
|
||||
action: user ? {
|
||||
label: 'Add Your First Spot',
|
||||
|
|
|
|||
Loading…
Reference in a new issue