Skip to content

Commit c31c8e5

Browse files
committed
feat: add searchDocumentation tool for querying Rocketadmin documentation and update prompts for AI responses
1 parent fe0b43a commit c31c8e5

6 files changed

Lines changed: 505 additions & 2 deletions

File tree

backend/src/ai-core/tools/database-tools.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ export function createDatabaseTools(isMongoDB: boolean): AIToolDefinition[] {
5353
},
5454
};
5555

56+
const searchDocumentationTool: AIToolDefinition = {
57+
name: 'searchDocumentation',
58+
description:
59+
'Searches the official Rocketadmin documentation at https://docs.rocketadmin.com and returns the most relevant pages with their titles, URLs, and content snippets. Use this when the user asks how to use Rocketadmin features (connections, dashboards, permissions, groups, master password, widgets, integrations, settings, SSO, secrets, etc.) or when a question is about the product rather than the data in the connected database.',
60+
parameters: {
61+
type: 'object',
62+
properties: {
63+
query: {
64+
type: 'string',
65+
description: 'A short search query describing what to look up in the Rocketadmin documentation.',
66+
},
67+
},
68+
required: ['query'],
69+
additionalProperties: false,
70+
},
71+
};
72+
5673
const tools: AIToolDefinition[] = [getTableStructureTool];
5774

5875
if (isMongoDB) {
@@ -61,6 +78,8 @@ export function createDatabaseTools(isMongoDB: boolean): AIToolDefinition[] {
6178
tools.push(executeRawSqlTool);
6279
}
6380

81+
tools.push(searchDocumentationTool);
82+
6483
return tools;
6584
}
6685

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import axios from 'axios';
2+
3+
const ALGOLIA_APP_ID = '31P3X3M1EE';
4+
const ALGOLIA_SEARCH_API_KEY = 'fe7422b190b4ec77f8e60c80a3a3ed8a';
5+
const ALGOLIA_INDEX_NAME = 'rocketadmin-docs';
6+
const ALGOLIA_SEARCH_URL = `https://${ALGOLIA_APP_ID}-dsn.algolia.net/1/indexes/${ALGOLIA_INDEX_NAME}/query`;
7+
8+
const DEFAULT_HITS_PER_PAGE = 5;
9+
const MAX_HITS_PER_PAGE = 10;
10+
const MAX_CONTENT_LENGTH = 800;
11+
const REQUEST_TIMEOUT_MS = 10000;
12+
13+
export interface DocumentationSearchHit {
14+
title: string;
15+
url: string;
16+
content: string;
17+
}
18+
19+
interface AlgoliaHierarchy {
20+
lvl0?: string | null;
21+
lvl1?: string | null;
22+
lvl2?: string | null;
23+
lvl3?: string | null;
24+
lvl4?: string | null;
25+
lvl5?: string | null;
26+
lvl6?: string | null;
27+
}
28+
29+
interface AlgoliaHit {
30+
url?: string;
31+
content?: string | null;
32+
hierarchy?: AlgoliaHierarchy;
33+
type?: string;
34+
}
35+
36+
interface AlgoliaSearchResponse {
37+
hits: AlgoliaHit[];
38+
}
39+
40+
export async function searchDocumentation(query: string, hitsPerPage?: number): Promise<DocumentationSearchHit[]> {
41+
const trimmedQuery = query?.trim();
42+
if (!trimmedQuery) {
43+
return [];
44+
}
45+
46+
const limit = Math.min(Math.max(hitsPerPage ?? DEFAULT_HITS_PER_PAGE, 1), MAX_HITS_PER_PAGE);
47+
48+
const response = await axios.post<AlgoliaSearchResponse>(
49+
ALGOLIA_SEARCH_URL,
50+
{
51+
query: trimmedQuery,
52+
hitsPerPage: limit,
53+
attributesToRetrieve: ['hierarchy', 'content', 'url', 'type'],
54+
attributesToSnippet: ['content:50'],
55+
},
56+
{
57+
headers: {
58+
'X-Algolia-Application-Id': ALGOLIA_APP_ID,
59+
'X-Algolia-API-Key': ALGOLIA_SEARCH_API_KEY,
60+
'Content-Type': 'application/json',
61+
},
62+
timeout: REQUEST_TIMEOUT_MS,
63+
},
64+
);
65+
66+
const hits = response.data?.hits ?? [];
67+
return hits.map(buildHit).filter((hit) => hit.url && (hit.title || hit.content));
68+
}
69+
70+
function buildHit(hit: AlgoliaHit): DocumentationSearchHit {
71+
const title = formatHierarchy(hit.hierarchy);
72+
const rawContent = (hit.content ?? '').replace(/\s+/g, ' ').trim();
73+
const content = rawContent.length > MAX_CONTENT_LENGTH ? `${rawContent.slice(0, MAX_CONTENT_LENGTH)}…` : rawContent;
74+
return {
75+
title,
76+
url: hit.url ?? '',
77+
content,
78+
};
79+
}
80+
81+
function formatHierarchy(hierarchy: AlgoliaHierarchy | undefined): string {
82+
if (!hierarchy) {
83+
return '';
84+
}
85+
const parts = [
86+
hierarchy.lvl0,
87+
hierarchy.lvl1,
88+
hierarchy.lvl2,
89+
hierarchy.lvl3,
90+
hierarchy.lvl4,
91+
hierarchy.lvl5,
92+
hierarchy.lvl6,
93+
]
94+
.filter((part): part is string => Boolean(part))
95+
.map((part) => part.replace(/||/g, '').trim())
96+
.filter(Boolean);
97+
return parts.join(' › ');
98+
}

backend/src/ai-core/tools/prompts.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Current date and time: ${currentDatetime}
1616
1717
Tool responses are encoded in TOON (Token-Oriented Object Notation) format - a compact, human-readable format similar to YAML with CSV-style tabular arrays. Parse it naturally.
1818
19-
Please follow these steps EXACTLY:
19+
Please follow these steps EXACTLY when answering data questions:
2020
1. First, always use the getTableStructure tool to analyze the table schema and understand available columns
2121
2. If the question requires data from related tables, note their relationships
2222
3. Generate an appropriate query that answers the user's question precisely
@@ -25,8 +25,13 @@ Please follow these steps EXACTLY:
2525
6. After receiving query results, explain them to the user in a clear, conversational way
2626
7. Include explanations of your approach when helpful
2727
28+
When the user asks how to use Rocketadmin itself (features, configuration, connections, dashboards, permissions, groups, master password, widgets, integrations, SSO, secrets, settings, API, etc.) rather than asking about the data in their database:
29+
- Call the searchDocumentation tool with a concise query that captures the user's question
30+
- Base your answer on the returned snippets and cite the relevant documentation URLs in your response
31+
- You may combine searchDocumentation with the data tools when a question needs both product knowledge and data from the database
32+
2833
IMPORTANT:
29-
- You MUST execute your generated queries using the appropriate tool - this is required for every question
34+
- You MUST execute your generated queries using the appropriate tool - this is required for every data question
3035
- After generating a SQL query, immediately call executeRawSql with that query
3136
- For MongoDB databases, call executeAggregationPipeline with the aggregation pipeline
3237
- The user cannot see the query results until you execute it with the appropriate tool

backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { AIToolCall, AIToolDefinition } from '../../../ai-core/interfaces/ai-pro
1010
import { AIProviderType } from '../../../ai-core/interfaces/ai-service.interface.js';
1111
import { AICoreService } from '../../../ai-core/services/ai-core.service.js';
1212
import { createDatabaseTools } from '../../../ai-core/tools/database-tools.js';
13+
import { searchDocumentation } from '../../../ai-core/tools/documentation-search.js';
1314
import { createDatabaseQuerySystemPrompt } from '../../../ai-core/tools/prompts.js';
1415
import { isValidMongoDbCommand, isValidSQLQuery, wrapQueryWithLimit } from '../../../ai-core/tools/query-validators.js';
1516
import { MessageBuilder } from '../../../ai-core/utils/message-builder.js';
@@ -275,6 +276,16 @@ export class RequestInfoFromTableWithAIUseCaseV7
275276
break;
276277
}
277278

279+
case 'searchDocumentation': {
280+
const query = toolCall.arguments.query as string;
281+
if (!query) {
282+
throw new Error('Missing required function argument "query"');
283+
}
284+
const docsResults = await searchDocumentation(query);
285+
result = encodeToToon({ query, results: docsResults });
286+
break;
287+
}
288+
278289
default:
279290
result = encodeError({ error: `Unknown tool: ${toolCall.name}` });
280291
}

0 commit comments

Comments
 (0)