@sisu-ai/mw-tool-calling 1.0.0
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 +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +78 -0
- package/package.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @sisu-ai/mw-tool-calling
|
|
2
|
+
|
|
3
|
+
Native tools API loop for providers that support tool calls.
|
|
4
|
+
|
|
5
|
+
## Behavior
|
|
6
|
+
- First turn: calls `ctx.model.generate(messages, { tools, toolChoice:'auto' })`.
|
|
7
|
+
- If assistant returns `tool_calls`, appends the assistant message and executes each tool.
|
|
8
|
+
- Executes each unique `(name, args)` once and responds to every `tool_call_id`.
|
|
9
|
+
- Handles provider quirks (e.g., missing args on duplicates) by reusing last args.
|
|
10
|
+
- Second turn: asks for a pure completion (`toolChoice:'none'`).
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
```ts
|
|
14
|
+
import { toolCalling } from '@sisu-ai/mw-tool-calling';
|
|
15
|
+
|
|
16
|
+
const app = new Agent()
|
|
17
|
+
.use(toolCalling);
|
|
18
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export const toolCalling = async (ctx, next) => {
|
|
2
|
+
await next();
|
|
3
|
+
for (let i = 0; i < 6; i++) {
|
|
4
|
+
ctx.log.debug?.('[tool-calling] iteration start', { i, messages: ctx.messages.length });
|
|
5
|
+
const toolList = ctx.tools.list();
|
|
6
|
+
const allowTools = i === 0 ? 'auto' : 'none';
|
|
7
|
+
const genOpts = { toolChoice: allowTools, signal: ctx.signal };
|
|
8
|
+
if (allowTools !== 'none') {
|
|
9
|
+
genOpts.tools = toolList;
|
|
10
|
+
genOpts.parallelToolCalls = false;
|
|
11
|
+
}
|
|
12
|
+
const out = await ctx.model.generate(ctx.messages, genOpts);
|
|
13
|
+
const msg = out.message;
|
|
14
|
+
const toolCalls = msg.tool_calls;
|
|
15
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
16
|
+
// Important: include the assistant message that requested tools so tool_call_id has a valid anchor.
|
|
17
|
+
ctx.messages.push(msg);
|
|
18
|
+
ctx.log.info?.('[tool-calling] model requested tools', toolCalls.map(tc => ({ id: tc.id, name: tc.name, hasArgs: typeof tc.arguments !== 'undefined' })));
|
|
19
|
+
// Execute each unique (name,args) once, but reply to every tool_call_id.
|
|
20
|
+
const cache = new Map();
|
|
21
|
+
const keyOf = (tc) => `${tc.name}:${safeStableStringify(tc.arguments)}`;
|
|
22
|
+
const lastArgsByName = new Map();
|
|
23
|
+
// Pre-pass: fill missing arguments from last seen arguments of same tool name (provider quirk)
|
|
24
|
+
const resolvedCalls = toolCalls.map((tc) => {
|
|
25
|
+
if (typeof tc.arguments === 'undefined' && lastArgsByName.has(tc.name)) {
|
|
26
|
+
return { ...tc, arguments: lastArgsByName.get(tc.name) };
|
|
27
|
+
}
|
|
28
|
+
return tc;
|
|
29
|
+
});
|
|
30
|
+
for (const call of resolvedCalls) {
|
|
31
|
+
const tool = ctx.tools.get(call.name);
|
|
32
|
+
if (!tool)
|
|
33
|
+
throw new Error('Unknown tool: ' + call.name);
|
|
34
|
+
const key = keyOf(call);
|
|
35
|
+
let result = cache.get(key);
|
|
36
|
+
if (result === undefined) {
|
|
37
|
+
const args = tool.schema?.parse ? tool.schema.parse(call.arguments) : call.arguments;
|
|
38
|
+
ctx.log.debug?.('[tool-calling] invoking tool', { name: call.name, id: call.id, args });
|
|
39
|
+
result = await tool.handler(args, ctx);
|
|
40
|
+
cache.set(key, result);
|
|
41
|
+
lastArgsByName.set(call.name, args);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
ctx.log.debug?.('[tool-calling] reusing cached tool result for duplicate call', { name: call.name, id: call.id });
|
|
45
|
+
}
|
|
46
|
+
// Prefer tool_call_id when available (tools API)
|
|
47
|
+
const toolMsg = { role: 'tool', content: JSON.stringify(result) };
|
|
48
|
+
if (call.id)
|
|
49
|
+
toolMsg.tool_call_id = call.id;
|
|
50
|
+
else
|
|
51
|
+
toolMsg.name = call.name;
|
|
52
|
+
ctx.messages.push(toolMsg);
|
|
53
|
+
ctx.log.debug?.('[tool-calling] tool result appended', { name: call.name, id: call.id, contentBytes: toolMsg.content.length });
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
ctx.log.info?.('[tool-calling] no tool calls; appending assistant message');
|
|
59
|
+
ctx.messages.push(msg);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
function safeStableStringify(v) {
|
|
65
|
+
try {
|
|
66
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
67
|
+
const keys = Object.keys(v).sort();
|
|
68
|
+
const obj = {};
|
|
69
|
+
for (const k of keys)
|
|
70
|
+
obj[k] = v[k];
|
|
71
|
+
return JSON.stringify(obj);
|
|
72
|
+
}
|
|
73
|
+
return JSON.stringify(v);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return String(v);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sisu-ai/mw-tool-calling",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -b"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@sisu-ai/core": "0.2.0"
|
|
18
|
+
}
|
|
19
|
+
}
|