Merge pull request #141 from IronRookieCoder/fix/search-extra-tools-discover-schema
fix(search-extra-tools): return input schema in discover mode results
This commit is contained in:
commit
02827e97cd
|
|
@ -58,6 +58,17 @@ export const outputSchema = lazySchema(() =>
|
||||||
pending_mcp_servers: z.array(z.string()).optional(),
|
pending_mcp_servers: z.array(z.string()).optional(),
|
||||||
/** Matches that are already loaded (core tools) and can be called directly. */
|
/** Matches that are already loaded (core tools) and can be called directly. */
|
||||||
already_loaded: z.array(z.string()).optional(),
|
already_loaded: z.array(z.string()).optional(),
|
||||||
|
/** Tool details (name, description, schema) for discover mode. */
|
||||||
|
details: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
score: z.number(),
|
||||||
|
inputSchema: z.record(z.string(), z.unknown()).optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
type OutputSchema = ReturnType<typeof outputSchema>
|
type OutputSchema = ReturnType<typeof outputSchema>
|
||||||
|
|
@ -131,6 +142,7 @@ function buildSearchResult(
|
||||||
totalDeferredTools: number,
|
totalDeferredTools: number,
|
||||||
pendingMcpServers?: string[],
|
pendingMcpServers?: string[],
|
||||||
alreadyLoaded?: string[],
|
alreadyLoaded?: string[],
|
||||||
|
details?: Output['details'],
|
||||||
): { data: Output } {
|
): { data: Output } {
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -143,6 +155,7 @@ function buildSearchResult(
|
||||||
...(alreadyLoaded && alreadyLoaded.length > 0
|
...(alreadyLoaded && alreadyLoaded.length > 0
|
||||||
? { already_loaded: alreadyLoaded }
|
? { already_loaded: alreadyLoaded }
|
||||||
: {}),
|
: {}),
|
||||||
|
...(details && details.length > 0 ? { details } : {}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -446,17 +459,12 @@ export const SearchExtraToolsTool = buildTool({
|
||||||
const discoverQuery = discoverMatch[1]!.trim()
|
const discoverQuery = discoverMatch[1]!.trim()
|
||||||
const index = await getToolIndex(deferredTools)
|
const index = await getToolIndex(deferredTools)
|
||||||
const tfIdfResults = searchTools(discoverQuery, index, max_results)
|
const tfIdfResults = searchTools(discoverQuery, index, max_results)
|
||||||
const textResults = tfIdfResults.map(r => {
|
const details = tfIdfResults.map(r => ({
|
||||||
let line = `**${r.name}** (score: ${r.score.toFixed(2)})\n${r.description}`
|
name: r.name,
|
||||||
if (r.inputSchema) {
|
description: r.description,
|
||||||
line += `\nSchema: ${JSON.stringify(r.inputSchema)}`
|
score: r.score,
|
||||||
}
|
...(r.inputSchema ? { inputSchema: r.inputSchema as Record<string, unknown> } : {}),
|
||||||
return line
|
}))
|
||||||
})
|
|
||||||
const text =
|
|
||||||
textResults.length > 0
|
|
||||||
? `Found ${textResults.length} tools:\n${textResults.join('\n\n')}`
|
|
||||||
: 'No matching deferred tools found'
|
|
||||||
logSearchOutcome(
|
logSearchOutcome(
|
||||||
tfIdfResults.map(r => r.name),
|
tfIdfResults.map(r => r.name),
|
||||||
'keyword',
|
'keyword',
|
||||||
|
|
@ -465,6 +473,9 @@ export const SearchExtraToolsTool = buildTool({
|
||||||
tfIdfResults.map(r => r.name),
|
tfIdfResults.map(r => r.name),
|
||||||
query,
|
query,
|
||||||
deferredTools.length,
|
deferredTools.length,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
details,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,6 +570,26 @@ export const SearchExtraToolsTool = buildTool({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discover mode: render tool details with name, description, and input schema.
|
||||||
|
// This lets the model inspect parameters before selecting and invoking a tool.
|
||||||
|
if (content.details && content.details.length > 0) {
|
||||||
|
const lines = content.details.map(d => {
|
||||||
|
let line = `**${d.name}** (score: ${d.score.toFixed(2)})\n${d.description}`
|
||||||
|
if (d.inputSchema) {
|
||||||
|
line += `\nSchema: ${JSON.stringify(d.inputSchema)}`
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
})
|
||||||
|
const text =
|
||||||
|
`Found ${lines.length} tool(s) matching your query:\n\n` +
|
||||||
|
lines.join('\n\n')
|
||||||
|
return {
|
||||||
|
type: 'tool_result',
|
||||||
|
tool_use_id: toolUseID,
|
||||||
|
content: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Separate already-loaded (core) tools from truly deferred tools
|
// Separate already-loaded (core) tools from truly deferred tools
|
||||||
const alreadyLoadedNames = content.already_loaded ?? []
|
const alreadyLoadedNames = content.already_loaded ?? []
|
||||||
const deferredNames = content.matches.filter(
|
const deferredNames = content.matches.filter(
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ function makeContext(tools: unknown[] = []) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('SearchExtraToolsTool search enhancements', () => {
|
describe('SearchExtraToolsTool search enhancements', () => {
|
||||||
test('discover: prefix triggers TF-IDF search and returns matches', async () => {
|
test('discover: prefix triggers TF-IDF search and returns matches with details', async () => {
|
||||||
const mockTool = makeDeferredTool('CronCreate', 'Schedule cron jobs')
|
const mockTool = makeDeferredTool('CronCreate', 'Schedule cron jobs')
|
||||||
mockGetToolIndex.mockResolvedValueOnce([])
|
mockGetToolIndex.mockResolvedValueOnce([])
|
||||||
mockSearchTools.mockReturnValueOnce([
|
mockSearchTools.mockReturnValueOnce([
|
||||||
|
|
@ -112,11 +112,11 @@ describe('SearchExtraToolsTool search enhancements', () => {
|
||||||
score: 0.85,
|
score: 0.85,
|
||||||
isMcp: false,
|
isMcp: false,
|
||||||
isDeferred: true,
|
isDeferred: true,
|
||||||
inputSchema: undefined,
|
inputSchema: { interval: { type: 'string' } },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const result: { data: { matches: string[] } } = await (
|
const result: { data: { matches: string[]; details?: Array<{ name: string; description: string; score: number; inputSchema?: Record<string, unknown> }> } } = await (
|
||||||
SearchExtraToolsTool as any
|
SearchExtraToolsTool as any
|
||||||
).call(
|
).call(
|
||||||
{ query: 'discover:schedule cron job', max_results: 5 },
|
{ query: 'discover:schedule cron job', max_results: 5 },
|
||||||
|
|
@ -127,6 +127,11 @@ describe('SearchExtraToolsTool search enhancements', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result.data.matches).toContain('CronCreate')
|
expect(result.data.matches).toContain('CronCreate')
|
||||||
|
expect(result.data.details).toBeDefined()
|
||||||
|
expect(result.data.details!.length).toBe(1)
|
||||||
|
expect(result.data.details![0]!.name).toBe('CronCreate')
|
||||||
|
expect(result.data.details![0]!.description).toBe('Schedule cron jobs')
|
||||||
|
expect(result.data.details![0]!.inputSchema).toEqual({ interval: { type: 'string' } })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('keyword + TF-IDF parallel search merges results', async () => {
|
test('keyword + TF-IDF parallel search merges results', async () => {
|
||||||
|
|
@ -232,4 +237,29 @@ describe('SearchExtraToolsTool search enhancements', () => {
|
||||||
|
|
||||||
expect(blockParam.content).toContain('No matching deferred tools found')
|
expect(blockParam.content).toContain('No matching deferred tools found')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('discover mode renders details with description and schema', async () => {
|
||||||
|
const blockParam = SearchExtraToolsTool.mapToolResultToToolResultBlockParam(
|
||||||
|
{
|
||||||
|
matches: ['CronCreate'],
|
||||||
|
query: 'discover:schedule',
|
||||||
|
total_deferred_tools: 5,
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
name: 'CronCreate',
|
||||||
|
description: 'Schedule cron jobs',
|
||||||
|
score: 0.85,
|
||||||
|
inputSchema: { interval: { type: 'string' } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'tool-use-123',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(typeof blockParam.content).toBe('string')
|
||||||
|
expect(blockParam.content as string).toContain('CronCreate')
|
||||||
|
expect(blockParam.content as string).toContain('Schedule cron jobs')
|
||||||
|
expect(blockParam.content as string).toContain('Schema')
|
||||||
|
expect(blockParam.content as string).toContain('"interval"')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ IMPORTANT: ExecuteExtraTool is always available in your tool list. After this se
|
||||||
|
|
||||||
Query forms:
|
Query forms:
|
||||||
- "select:CronCreate,Snip" — fetch these exact tools by name
|
- "select:CronCreate,Snip" — fetch these exact tools by name
|
||||||
- "discover:schedule cron job" — pure discovery, returns tool info (name, description) without loading. Use when you want to understand available tools before deciding which to invoke.
|
- "discover:schedule cron job" — pure discovery, returns tool info (name, description, input schema) without loading. Use when you need to understand a tool's parameters before invoking it, then call ExecuteExtraTool with the discovered tool name and params.
|
||||||
- "notebook jupyter" — keyword search, up to max_results best matches
|
- "notebook jupyter" — keyword search, up to max_results best matches
|
||||||
- "+slack send" — require "slack" in the name, rank by remaining terms`
|
- "+slack send" — require "slack" in the name, rank by remaining terms`
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user