Files
2026-07-13 13:34:48 +08:00

126 lines
3.8 KiB
TypeScript

/**
* Example 16: Advanced Tool Chaining
*
* This example demonstrates sophisticated tool chaining and data flow in worker mode.
* It shows how to:
* - Chain multiple tools with complex data dependencies
* - Pass data between tools with type-safe schemas
* - Perform data transformations and filtering between tool calls
* - Execute complex multi-step workflows in a single turn
* - Handle nested object structures and array operations
*
* Key concepts:
* - Multi-tool orchestration in single execution
* - Complex data flow between tools
* - Type-safe data extraction and transformation
* - Single-turn complex workflows
* - Demonstration of LLMz's superiority over traditional tool calling
*/
import { Client } from '@botpress/client'
import { z } from '@bpinternal/zui'
import chalk from 'chalk'
import { execute, Exit, Tool } from 'llmz'
import { box } from '../utils/box'
// Initialize Botpress client
const client = new Client({
botId: process.env.BOTPRESS_BOT_ID!,
token: process.env.BOTPRESS_TOKEN!,
})
// This example demonstrates the power of LLMz's code generation approach:
// Tool C requires:
// 1. A deep nested number from Tool A
// 2. Filtered array data from Tool B (only numbers > 50)
// All of this complex orchestration happens in a SINGLE LLM turn
// Traditional JSON tool calling would require multiple expensive roundtrips
// Tool A: Generates a random number in a deeply nested structure
// This demonstrates how LLMz handles complex object schemas
const ToolA = new Tool({
name: 'tool_a',
output: z.object({
pick: z.object({
deep: z.object({
deep_number: z.number(),
}),
}),
}),
async handler() {
const deep_number = Math.floor(Math.random() * 100)
console.log('Tool A executed, returning number:', deep_number)
return {
pick: {
deep: {
deep_number,
},
},
}
},
})
// Tool B: Generates an array of random numbers
// The LLM will need to filter this array for Tool C
const ToolB = new Tool({
name: 'tool_b',
output: z.number().array(),
async handler() {
const array = Array.from({ length: 10 }, () => Math.floor(Math.random() * 100))
console.log('Tool B executed, returning array:', array)
return array
},
})
// Tool C: Consumes processed data from both Tool A and Tool B
// This demonstrates complex data dependencies and processing
const ToolC = new Tool({
name: 'tool_c',
input: z.object({
first_task: z.number().describe('Number from tool A'),
second_task: z.number().array().describe('Numbers from tool B that are greater than 50'),
}),
output: z.number().describe("The 'secret' number"),
async handler({ first_task, second_task }) {
console.log('Tool C executed with input:', { first_task, second_task })
// Compute the final "secret" number by combining the inputs
return first_task + second_task.reduce((acc, num) => acc + num, 0)
},
})
// Exit condition to capture the final result
const exit = new Exit({
name: 'exit',
description: 'Exit the program',
schema: z.object({
result: z.number(),
}),
})
// Execute the complex workflow
// The LLM will generate code that:
// 1. Calls Tool A and extracts the deep nested number
// 2. Calls Tool B and filters the array for numbers > 50
// 3. Calls Tool C with the processed data
// 4. Returns the final result through the exit
const result = await execute({
instructions: "I need the 'secret' number please. Do not think, try to do it in one step.",
tools: [ToolA, ToolB, ToolC],
exits: [exit],
client,
})
// Display the results showing both the generated code and final output
if (result.is(exit)) {
console.log(
box([
'The LLM wrote the code to solve the problem:',
...result.iteration.code!.split('\n'),
'',
'It then executed it and returned the result:',
chalk.cyan.bold(result.output.result.toString()),
])
)
}