@jambonz/tools 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +310 -0
- package/dist/index.cjs +482 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +139 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +450 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# @jambonz/tools
|
|
2
|
+
|
|
3
|
+
Pre-built, reusable tools for jambonz pipeline voice AI agents.
|
|
4
|
+
|
|
5
|
+
Instead of copy-pasting tool schemas and writing API handlers for every application, import a ready-made tool and wire it up in a few lines:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { createTavilySearch, registerTools } from '@jambonz/tools';
|
|
9
|
+
|
|
10
|
+
const search = createTavilySearch({ apiKey: 'tvly-xxx' });
|
|
11
|
+
|
|
12
|
+
session.pipeline({
|
|
13
|
+
llm: {
|
|
14
|
+
vendor: 'openai',
|
|
15
|
+
model: 'gpt-4.1-mini',
|
|
16
|
+
llmOptions: {
|
|
17
|
+
messages: [{ role: 'system', content: 'You are a helpful assistant.' }],
|
|
18
|
+
tools: [search.schema],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
toolHook: '/tool-call',
|
|
22
|
+
// ...stt, tts, etc.
|
|
23
|
+
}).send();
|
|
24
|
+
|
|
25
|
+
registerTools(session, '/tool-call', [search]);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
That's it. The schema tells the LLM what the tool does, `registerTools` handles dispatch and execution.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @jambonz/tools
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Concepts
|
|
37
|
+
|
|
38
|
+
### How tools work in jambonz pipeline
|
|
39
|
+
|
|
40
|
+
When you give an LLM tools in a jambonz pipeline, the flow is:
|
|
41
|
+
|
|
42
|
+
1. The LLM decides to call a tool and sends a `tool_call` with a name and arguments
|
|
43
|
+
2. The feature-server routes it to your application via the `toolHook` path
|
|
44
|
+
3. Your application executes the tool and sends the result back via `session.sendToolOutput()`
|
|
45
|
+
4. The LLM incorporates the result into its response
|
|
46
|
+
|
|
47
|
+
This package handles step 3 for you — each tool bundles a pre-built `execute()` function that calls the right API, parses the result, and returns a text string the LLM can use.
|
|
48
|
+
|
|
49
|
+
### The JambonzTool interface
|
|
50
|
+
|
|
51
|
+
Every tool in this package implements:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface JambonzTool {
|
|
55
|
+
schema: ToolSchema; // what the LLM sees
|
|
56
|
+
execute(args: Record<string, any>): Promise<string>; // what runs when the LLM calls it
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can use these two pieces independently:
|
|
61
|
+
- Pass `tool.schema` to `llmOptions.tools` in your pipeline verb
|
|
62
|
+
- Call `tool.execute(args)` yourself in a custom toolHook handler
|
|
63
|
+
- Or use `registerTools()` to wire everything up automatically
|
|
64
|
+
|
|
65
|
+
### registerTools()
|
|
66
|
+
|
|
67
|
+
`registerTools()` is a convenience that listens on a session's toolHook path and dispatches to the right tool:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
registerTools(session, '/tool-call', [search, weather, calculator], {
|
|
71
|
+
logger: log, // optional pino logger — logs tool calls and errors
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It handles:
|
|
76
|
+
- Matching the tool name from the LLM's request to the right handler
|
|
77
|
+
- Calling `execute()` with the parsed arguments
|
|
78
|
+
- Sending the result back via `session.sendToolOutput()`
|
|
79
|
+
- Error handling — if a tool throws, the error message is sent back to the LLM
|
|
80
|
+
- Unknown tools — if the LLM calls a tool that isn't registered, an error is returned
|
|
81
|
+
|
|
82
|
+
## Available tools
|
|
83
|
+
|
|
84
|
+
### Web Search (Tavily)
|
|
85
|
+
|
|
86
|
+
Search the web for current information. Requires a [Tavily API key](https://tavily.com).
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { createTavilySearch } from '@jambonz/tools';
|
|
90
|
+
|
|
91
|
+
const search = createTavilySearch({
|
|
92
|
+
apiKey: 'tvly-xxx', // required
|
|
93
|
+
maxResults: 3, // default: 3
|
|
94
|
+
searchDepth: 'basic', // 'basic' | 'advanced' (default: 'basic')
|
|
95
|
+
topic: 'general', // 'general' | 'news' | 'finance' (default: 'general')
|
|
96
|
+
includeDomains: ['cnn.com'], // optional: only search these domains
|
|
97
|
+
excludeDomains: ['x.com'], // optional: never include these domains
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Tool name:** `web_search`
|
|
102
|
+
**Parameters the LLM sends:** `{ query: "latest news about..." }`
|
|
103
|
+
|
|
104
|
+
### Weather (Open-Meteo)
|
|
105
|
+
|
|
106
|
+
Current weather for any location worldwide. **No API key required.**
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { createWeather } from '@jambonz/tools';
|
|
110
|
+
|
|
111
|
+
const weather = createWeather({
|
|
112
|
+
scale: 'fahrenheit', // 'celsius' | 'fahrenheit' (default: 'celsius')
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Tool name:** `get_weather`
|
|
117
|
+
**Parameters the LLM sends:** `{ location: "San Francisco" }`
|
|
118
|
+
**Returns:** Temperature, feels-like, wind speed, humidity, and conditions.
|
|
119
|
+
|
|
120
|
+
### Wikipedia
|
|
121
|
+
|
|
122
|
+
Factual summaries from Wikipedia. **No API key required.**
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { createWikipedia } from '@jambonz/tools';
|
|
126
|
+
|
|
127
|
+
const wiki = createWikipedia({
|
|
128
|
+
maxSentences: 5, // default: 5
|
|
129
|
+
language: 'en', // Wikipedia language edition (default: 'en')
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Tool name:** `wikipedia`
|
|
134
|
+
**Parameters the LLM sends:** `{ query: "Eiffel Tower" }`
|
|
135
|
+
**Returns:** Article title and summary text.
|
|
136
|
+
|
|
137
|
+
### Calculator
|
|
138
|
+
|
|
139
|
+
Safe math expression evaluator. **No API key required.**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { createCalculator } from '@jambonz/tools';
|
|
143
|
+
|
|
144
|
+
const calc = createCalculator();
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Tool name:** `calculator`
|
|
148
|
+
**Parameters the LLM sends:** `{ expression: "87.50 * 0.15" }`
|
|
149
|
+
**Supports:** `+`, `-`, `*`, `/`, `^` (power), `%` (modulo), parentheses, and functions: `sqrt`, `abs`, `round`, `ceil`, `floor`, `sin`, `cos`, `tan`, `log`, `log10`, `exp`. Constants: `pi`, `e`.
|
|
150
|
+
|
|
151
|
+
Uses a safe recursive descent parser — no `eval()`.
|
|
152
|
+
|
|
153
|
+
### Date & Time
|
|
154
|
+
|
|
155
|
+
Current date and time for any timezone. **No API key required.**
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { createDateTime } from '@jambonz/tools';
|
|
159
|
+
|
|
160
|
+
const datetime = createDateTime({
|
|
161
|
+
defaultTimezone: 'America/New_York', // default: 'UTC'
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Tool name:** `get_datetime`
|
|
166
|
+
**Parameters the LLM sends:** `{ timezone: "Asia/Tokyo" }` (optional — uses default if omitted)
|
|
167
|
+
**Returns:** Formatted date, time, and timezone. Handles common city names ("London", "Tokyo") in addition to IANA zones.
|
|
168
|
+
|
|
169
|
+
## Full example
|
|
170
|
+
|
|
171
|
+
A voice agent with web search, weather, and a calculator:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import * as http from 'node:http';
|
|
175
|
+
import pino from 'pino';
|
|
176
|
+
import { createEndpoint } from '@jambonz/sdk/websocket';
|
|
177
|
+
import {
|
|
178
|
+
createTavilySearch,
|
|
179
|
+
createWeather,
|
|
180
|
+
createCalculator,
|
|
181
|
+
registerTools,
|
|
182
|
+
} from '@jambonz/tools';
|
|
183
|
+
|
|
184
|
+
const logger = pino({ level: 'info' });
|
|
185
|
+
const port = parseInt(process.env.PORT || '3000', 10);
|
|
186
|
+
const server = http.createServer();
|
|
187
|
+
const makeService = createEndpoint({ server, port });
|
|
188
|
+
|
|
189
|
+
const search = createTavilySearch({ apiKey: process.env.TAVILY_API_KEY! });
|
|
190
|
+
const weather = createWeather({ scale: 'fahrenheit' });
|
|
191
|
+
const calc = createCalculator();
|
|
192
|
+
const tools = [search, weather, calc];
|
|
193
|
+
|
|
194
|
+
const svc = makeService({ path: '/' });
|
|
195
|
+
svc.on('session:new', (session) => {
|
|
196
|
+
const log = logger.child({ call_sid: session.callSid });
|
|
197
|
+
|
|
198
|
+
session.on('/pipeline-complete', () => {
|
|
199
|
+
session.hangup().reply();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
registerTools(session, '/tool-call', tools, { logger: log });
|
|
203
|
+
|
|
204
|
+
session
|
|
205
|
+
.pipeline({
|
|
206
|
+
stt: { vendor: 'deepgram', language: 'multi' },
|
|
207
|
+
tts: { vendor: 'cartesia', voice: '9626c31c-bec5-4cca-baa8-f8ba9e84c8bc' },
|
|
208
|
+
llm: {
|
|
209
|
+
vendor: 'openai',
|
|
210
|
+
model: 'gpt-4.1-mini',
|
|
211
|
+
llmOptions: {
|
|
212
|
+
messages: [
|
|
213
|
+
{
|
|
214
|
+
role: 'system',
|
|
215
|
+
content: [
|
|
216
|
+
'You are a helpful voice assistant with access to web search, weather, and a calculator.',
|
|
217
|
+
'Use the appropriate tool when the user asks about current events, weather, or math.',
|
|
218
|
+
'Keep responses concise and conversational.',
|
|
219
|
+
].join(' '),
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
tools: tools.map((t) => t.schema),
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
toolHook: '/tool-call',
|
|
226
|
+
bargeIn: { enable: true },
|
|
227
|
+
turnDetection: 'krisp',
|
|
228
|
+
actionHook: '/pipeline-complete',
|
|
229
|
+
})
|
|
230
|
+
.send();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
logger.info({ port }, 'listening');
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Mixing with custom tools and MCP
|
|
237
|
+
|
|
238
|
+
`@jambonz/tools` works alongside your own custom tools and MCP servers. Just include whatever you need:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// your custom tool
|
|
242
|
+
const myTool = {
|
|
243
|
+
name: 'lookup_order',
|
|
244
|
+
description: 'Look up an order by ID',
|
|
245
|
+
parameters: { type: 'object', properties: { order_id: { type: 'string' } }, required: ['order_id'] },
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
session.pipeline({
|
|
249
|
+
llm: {
|
|
250
|
+
llmOptions: {
|
|
251
|
+
tools: [
|
|
252
|
+
search.schema, // from @jambonz/tools
|
|
253
|
+
weather.schema, // from @jambonz/tools
|
|
254
|
+
myTool, // your custom schema
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
mcpServers: [ // MCP tools discovered automatically
|
|
259
|
+
{ url: 'https://mcp.example.com/sse' },
|
|
260
|
+
],
|
|
261
|
+
toolHook: '/tool-call',
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// register the pre-built tools
|
|
265
|
+
registerTools(session, '/tool-call', [search, weather], { logger: log });
|
|
266
|
+
|
|
267
|
+
// handle your custom tool separately
|
|
268
|
+
session.on('/tool-call', async (evt) => {
|
|
269
|
+
if (evt.name === 'lookup_order') {
|
|
270
|
+
const result = await db.orders.find(evt.arguments.order_id);
|
|
271
|
+
session.sendToolOutput(evt.tool_call_id, JSON.stringify(result));
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
> **Note:** MCP tools are handled by the feature-server directly — they don't arrive on your toolHook. Only inline tools (from `llmOptions.tools`) route to your application.
|
|
277
|
+
|
|
278
|
+
## Using with dynamic tools
|
|
279
|
+
|
|
280
|
+
You can inject `@jambonz/tools` tools mid-conversation using `updatePipeline()`:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// start without tools, then add them after a few turns
|
|
284
|
+
session.updatePipeline({
|
|
285
|
+
type: 'update_tools',
|
|
286
|
+
tools: [search.schema, weather.schema],
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Contributing a new tool
|
|
291
|
+
|
|
292
|
+
Adding a tool to this package:
|
|
293
|
+
|
|
294
|
+
1. Create `src/tools/your-tool.ts`
|
|
295
|
+
2. Export a factory function: `createYourTool(options?): JambonzTool`
|
|
296
|
+
3. The factory returns `{ schema, execute }`
|
|
297
|
+
4. Add the export to `src/index.ts`
|
|
298
|
+
5. Add documentation to this README
|
|
299
|
+
|
|
300
|
+
### Guidelines
|
|
301
|
+
|
|
302
|
+
- **Keep it simple** — a tool is a schema + an async function that returns a string. No classes, no inheritance.
|
|
303
|
+
- **Prefer free APIs** — tools that work without API keys have a lower barrier to entry. If an API key is required, make it the only required option.
|
|
304
|
+
- **Return text, not JSON** — the LLM reads the result as part of a conversation. Format output as natural language, not raw JSON.
|
|
305
|
+
- **Handle errors gracefully** — return a helpful message rather than throwing when possible (e.g., "No results found" rather than crashing).
|
|
306
|
+
- **Think voice-first** — the result will be spoken aloud. Avoid markdown, URLs, or formatting that sounds awkward when read by TTS.
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT
|