-
Notifications
You must be signed in to change notification settings - Fork 3
feat: add meta-tools for intelligent tool discovery and orchestration #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
66f5227
53f3269
7934e05
683eb7b
e25d80a
5e95417
83d1111
5369ecc
81a57ea
4dc6aea
ff5aa4c
ed0c19c
fe8c8ff
1309127
01eaf68
895f99d
d425713
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,128 @@ These toolsets provide functionality to filter, transform, and execute tools. Th | |
|
|
||
| Under the hood the StackOneToolSet uses the same OpenAPIParser as the OpenAPIToolSet, but provides some convenience methods for using StackOne API keys and account IDs. | ||
|
|
||
| ### Meta Tools (Beta) | ||
|
|
||
| > [!WARNING] | ||
| > Meta tools are currently in beta and their API may change in future versions. | ||
|
|
||
| StackOne AI SDK includes two powerful meta tools that enable AI agents to discover and orchestrate tool usage intelligently: | ||
|
|
||
| #### Discovering Relevant Tools | ||
|
|
||
| Use the `metaRelevantTool()` method to get a tool that discovers other tools based on natural language queries: | ||
|
|
||
| ```typescript | ||
| import { StackOneToolSet } from "@stackone/ai"; | ||
|
|
||
| const toolset = new StackOneToolSet(); | ||
| const tools = toolset.getTools("hris_*", "account-id"); | ||
|
|
||
| // Get the meta tool for finding relevant tools | ||
| const relevantToolsFinder = tools.metaRelevantTool(); | ||
|
|
||
| // Find tools for specific tasks | ||
| const result = await relevantToolsFinder.execute({ | ||
| query: "list employees", | ||
| limit: 5, | ||
| minScore: 0.5 | ||
| }); | ||
|
|
||
| // Returns tools ranked by relevance with scores and match reasons | ||
| ``` | ||
|
|
||
| #### Executing Tool Chains | ||
|
|
||
| Use the `metaExecuteTool()` method to orchestrate multiple tool executions with parameter passing: | ||
|
|
||
| ```typescript | ||
| const tools = toolset.getTools("hris_*", "account-id"); | ||
|
|
||
| // Get the meta tool for executing tool chains | ||
| const toolChain = tools.metaExecuteTool(); | ||
|
|
||
| // Execute a complex workflow | ||
| const result = await toolChain.execute({ | ||
| steps: [ | ||
| { | ||
| toolName: "hris_list_employees", | ||
| parameters: { page_size: "10" }, | ||
| stepName: "Get recent employees" | ||
| }, | ||
| { | ||
| toolName: "hris_get_employee", | ||
| parameters: { | ||
| id: "{{step0.result.items[0].id}}" // Reference previous results | ||
| }, | ||
| condition: "{{step0.result.items.length}} > 0", // Conditional execution | ||
| stepName: "Get employee details" | ||
| } | ||
| ], | ||
| accountId: "your-account-id" | ||
| }); | ||
| ``` | ||
|
|
||
| #### Getting Both Meta Tools | ||
|
|
||
| You can get both meta tools at once using the `metaTools()` method: | ||
|
|
||
| ```typescript | ||
| const tools = toolset.getTools("hris_*", "account-id"); | ||
|
|
||
| // Get both meta tools | ||
| const { metaRelevantTool, metaExecuteTool } = tools.metaTools(); | ||
|
|
||
| // Use them in your AI agent | ||
| const aiTools = new Tools([ | ||
| metaRelevantTool, | ||
| metaExecuteTool | ||
| ]).toAISDK(); | ||
| ``` | ||
|
|
||
| [View full examples](examples/meta-tools.ts) | ||
|
|
||
| ### Workflow Planning | ||
|
|
||
| While building agents you may find that your workflow is too complex for a general purpose agent. | ||
|
|
||
| StackOne AI SDK provides access to a state of the art planning agent which allows you to create, cache, and execute complex workflows on [verticals supported by StackOne](https://www.stackone.com/integrations). | ||
|
|
||
| For example, onboard a new hire from your ATS to your HRIS. | ||
|
|
||
| ```typescript | ||
| import { StackOneToolSet } from "@stackone/ai"; | ||
|
|
||
| const toolset = new StackOneToolSet(); | ||
|
|
||
| const onboardWorkflow = await toolset.plan({ | ||
| key: "custom_onboarding", | ||
| input: "Onboard the last new hire from Teamtailor to Workday", | ||
| model: "stackone-planner-latest", | ||
| tools: ["hris_*", "ats_*"], | ||
| accountIds: ["teamtailor_account_id", "workday_account_id"], | ||
| cache: true, // saves the plan to $HOME/.stackone/plans | ||
| }); | ||
|
|
||
| // Execute the workflow | ||
| await onboardWorkflow.execute(); | ||
| ``` | ||
|
|
||
| Or use it as a tool in a larger agent (using AI SDK) | ||
|
|
||
| ```typescript | ||
| await generateText({ | ||
| model: openai("gpt-4o"), | ||
| prompt: "You are a workplace agent, onboard the latest hires to our systems", | ||
|
Comment on lines
+99
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this |
||
| tools: onboardWorkflow.toAISDK(), | ||
| maxSteps: 3, | ||
| }); | ||
| ``` | ||
|
|
||
| > [!NOTE] | ||
| > The workflow planner is in closed beta and only available to design partners. Apply for the waitlist [here](https://www.stackone.com/demo). | ||
|
|
||
| [View full example](examples/planning.ts) | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
|
|
||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| /** | ||
| * Meta Tools Example | ||
| * | ||
| * This example demonstrates how to use the beta meta tools for intelligent | ||
| * tool discovery and orchestration. | ||
| * | ||
| * @beta These are beta features and may change in future versions | ||
| */ | ||
|
|
||
| import { openai } from '@ai-sdk/openai'; | ||
| import { generateText } from 'ai'; | ||
| import { StackOneToolSet, Tools } from '../src'; | ||
| import { ACCOUNT_IDS } from './constants'; | ||
|
|
||
| // Example 1: Using metaRelevantTool to discover tools | ||
| async function discoverToolsExample() { | ||
| console.log('\n=== Example 1: Discovering Relevant Tools ==='); | ||
|
|
||
| const toolset = new StackOneToolSet(); | ||
| const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS); | ||
|
|
||
| // Get the meta tool for finding relevant tools | ||
| const relevantToolsFinder = tools.metaRelevantTool(); | ||
|
|
||
| // Example queries for different use cases | ||
| const queries = [ | ||
| 'list employees', | ||
| 'get employee', | ||
| 'create employee', | ||
| 'list companies', | ||
| 'get company', | ||
| 'list applications', | ||
| ]; | ||
|
|
||
| for (const query of queries) { | ||
| console.log(`\nSearching for: "${query}"`); | ||
| const result = await relevantToolsFinder.execute({ | ||
| query, | ||
| limit: 3, | ||
| minScore: 0.5, | ||
| }); | ||
|
|
||
| console.log(`Found ${result.resultsCount} tools:`); | ||
| for (const tool of result.tools as Array<{ | ||
| name: string; | ||
| score: number; | ||
| matchReason: string; | ||
| }>) { | ||
| console.log(` - ${tool.name} (score: ${tool.score.toFixed(2)}) - ${tool.matchReason}`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Example 2: Using metaExecuteTool for complex workflows | ||
| async function toolChainExample() { | ||
| console.log('\n=== Example 2: Executing Tool Chains ==='); | ||
|
|
||
| const toolset = new StackOneToolSet(); | ||
| const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS); | ||
|
|
||
| // Get the meta tool for executing tool chains | ||
| const toolChain = tools.metaExecuteTool(); | ||
|
|
||
| // Example: Employee onboarding workflow | ||
| console.log('\nExecuting employee onboarding workflow...'); | ||
|
|
||
| const result = await toolChain.execute({ | ||
| steps: [ | ||
| { | ||
| toolName: 'hris_list_employees', | ||
| parameters: { | ||
| filter: { | ||
| updated_after: '2024-01-01T00:00:00Z', | ||
| }, | ||
| page_size: '10', | ||
| }, | ||
| stepName: 'Get recent employees', | ||
| }, | ||
| { | ||
| toolName: 'hris_get_employee', | ||
| parameters: { | ||
| id: '{{step0.result.items[0].id}}', // Get first employee ID | ||
| }, | ||
| stepName: 'Get employee details', | ||
| condition: '{{step0.result.items.length}} > 0', | ||
| }, | ||
| ], | ||
| accountId: ACCOUNT_IDS.HRIS, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we would want to inherit this from the toolset or the tools previously. we shouldnt supply account ids at runtime |
||
| }); | ||
|
|
||
| console.log(`\nWorkflow execution ${result.success ? 'succeeded' : 'failed'}`); | ||
| console.log(`Total execution time: ${result.executionTime}ms`); | ||
|
|
||
| for (const step of result.stepResults as Array<{ | ||
| stepIndex: number; | ||
| stepName: string; | ||
| success: boolean; | ||
| skipped?: boolean; | ||
| error?: string; | ||
| }>) { | ||
| console.log(`\nStep ${step.stepIndex + 1}: ${step.stepName}`); | ||
| console.log(` Status: ${step.success ? 'Success' : 'Failed'}`); | ||
| if (step.skipped) { | ||
| console.log(' Skipped due to condition'); | ||
| } | ||
| if (step.error) { | ||
| console.log(` Error: ${step.error}`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Example 3: Using meta tools with AI agents | ||
| async function aiAgentExample() { | ||
| console.log('\n=== Example 3: Meta Tools with AI Agents ==='); | ||
|
|
||
| // Check if OpenAI API key is set | ||
| if (!process.env.OPENAI_API_KEY) { | ||
| console.log('Skipping AI agent example - OPENAI_API_KEY environment variable not set'); | ||
| return; | ||
| } | ||
|
|
||
| const toolset = new StackOneToolSet(); | ||
| const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS); | ||
|
|
||
| // Get both meta tools | ||
| const { metaRelevantTool, metaExecuteTool } = tools.metaTools(); | ||
|
|
||
| // Convert meta tools to AI SDK format | ||
| const aiTools = new Tools([metaRelevantTool, metaExecuteTool]).toAISDK(); | ||
|
|
||
| try { | ||
| // Use with AI agent | ||
| const { text, toolCalls } = await generateText({ | ||
| model: openai('gpt-4o-mini'), | ||
| tools: aiTools, | ||
| prompt: `You are an HR assistant. First, use meta_relevant_tool to find tools | ||
| related to listing employees. Then use meta_execute_tool to get the | ||
| list of employees and fetch details for the first one.`, | ||
| maxSteps: 5, | ||
| }); | ||
|
|
||
| console.log('\nAI Agent Response:'); | ||
| console.log(text); | ||
|
|
||
| console.log('\nTool calls made:'); | ||
| for (const call of toolCalls || []) { | ||
| console.log(`- ${call.toolName}`); | ||
| } | ||
| } catch (error) { | ||
| console.error('Error in AI agent example:', error instanceof Error ? error.message : error); | ||
| } | ||
| } | ||
|
|
||
| // Example 4: Different ways to use meta tools | ||
| async function usageExample() { | ||
| console.log('\n=== Example 4: Different Ways to Use Meta Tools ==='); | ||
|
|
||
| const toolset = new StackOneToolSet(); | ||
|
|
||
| // Method 1: Get tools and then get meta tools | ||
| const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS); | ||
|
|
||
| // Get individual meta tools | ||
| const relevantTool = tools.metaRelevantTool(); | ||
| console.log(`Got meta tool: ${relevantTool.name}`); | ||
|
|
||
| const executeTool = tools.metaExecuteTool(); | ||
| console.log(`Got meta tool: ${executeTool.name}`); | ||
|
|
||
| // Get both meta tools at once | ||
| const { metaRelevantTool, metaExecuteTool } = tools.metaTools(); | ||
| console.log(`Got both meta tools: ${metaRelevantTool.name}, ${metaExecuteTool.name}`); | ||
| } | ||
|
|
||
| // Main execution | ||
| async function main() { | ||
| console.log('Meta Tools Examples (Beta)'); | ||
| console.log('=========================='); | ||
| console.log('Note: These are beta features and may change in future versions.\n'); | ||
|
|
||
| try { | ||
| await discoverToolsExample(); | ||
| await toolChainExample(); | ||
| await aiAgentExample(); | ||
| await usageExample(); | ||
| } catch (error) { | ||
| console.error('Error in examples:', error); | ||
| } | ||
| } | ||
|
|
||
| // Only run if this file is executed directly | ||
| if (import.meta.main) { | ||
| main(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| /** | ||
| * Beta warning constant for meta tools | ||
| */ | ||
| export const BETA_WARNING = 'This is a beta feature and may change in future versions.'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding account id here feels pretty weird. @glebedel what do you think?