@botbotgo/better-call 0.1.24 → 0.1.25
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 +12 -0
- package/docs/benchmark-lift.svg +24 -20
- package/docs/case-studies/stable-harness-tool-gateway.md +42 -0
- package/docs/faq.md +25 -0
- package/docs/marketing/growth-checklist.md +13 -7
- package/docs/recipes/README.md +11 -0
- package/docs/recipes/ollama-repair.md +33 -0
- package/docs/recipes/semantic-validator.md +25 -0
- package/docs/recipes/validate-only.md +17 -0
- package/docs/recipes/wrong-tool-name.md +24 -0
- package/docs/recipes/zod-schema.md +19 -0
- package/examples/README.md +7 -1
- package/examples/adapters/langchain-tool-wrapper.mjs +39 -0
- package/examples/adapters/mcp-gateway-boundary.mjs +51 -0
- package/examples/adapters/openai-compatible-repair.mjs +53 -0
- package/package.json +7 -1
- package/scripts/demo.mjs +97 -0
package/README.md
CHANGED
|
@@ -24,6 +24,12 @@ const tools = betterTools([searchTool, calculatorTool]);
|
|
|
24
24
|
|
|
25
25
|
No model means validate and block only. Add `repairModel` when you want automatic repair.
|
|
26
26
|
|
|
27
|
+
Try the built-in demo after installing:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @botbotgo/better-call demo
|
|
31
|
+
```
|
|
32
|
+
|
|
27
33
|
## Why BetterCall?
|
|
28
34
|
|
|
29
35
|
Small models often know that a tool is needed but still fail at the boundary: wrong tool names, malformed arguments, schema drift, extra fields, or raw tool-call-shaped text. BetterCall sits at that boundary and gives you a typed, auditable way to accept, repair, or reject the call before the real tool runs.
|
|
@@ -41,6 +47,9 @@ The `examples/` directory has copyable starting points:
|
|
|
41
47
|
- `examples/langchain-basic.mjs`: wrap a LangChain-style tool and repair bad args.
|
|
42
48
|
- `examples/ollama-small-model.mjs`: use an Ollama repair callback for a small local model.
|
|
43
49
|
- `examples/gateway-repair.mjs`: repair an invalid tool name at a custom runtime or MCP gateway boundary.
|
|
50
|
+
- `examples/adapters/langchain-tool-wrapper.mjs`: tiny adapter helper for LangChain-style tools.
|
|
51
|
+
- `examples/adapters/mcp-gateway-boundary.mjs`: MCP-style gateway boundary pattern.
|
|
52
|
+
- `examples/adapters/openai-compatible-repair.mjs`: OpenAI-compatible local endpoint repair callback.
|
|
44
53
|
|
|
45
54
|
## Installation
|
|
46
55
|
|
|
@@ -247,6 +256,9 @@ Latest completed remote run artifact: `benchmarks/bfcl-real-remote-completed-sum
|
|
|
247
256
|
- `docs/marketing/positioning.md`: product positioning, audience, differentiators, and calls to action.
|
|
248
257
|
- `docs/marketing/launch-post.md`: launch-post draft and short social version.
|
|
249
258
|
- `docs/marketing/growth-checklist.md`: channel-by-channel launch and adoption checklist.
|
|
259
|
+
- `docs/recipes/`: focused integration recipes.
|
|
260
|
+
- `docs/faq.md`: adoption and boundary FAQ.
|
|
261
|
+
- `docs/case-studies/stable-harness-tool-gateway.md`: production gateway integration pattern.
|
|
250
262
|
|
|
251
263
|
## Requirements
|
|
252
264
|
|
package/docs/benchmark-lift.svg
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="330" viewBox="0 0 760 330" role="img" aria-labelledby="title desc">
|
|
2
2
|
<title id="title">BetterCall BFCL small-model lift</title>
|
|
3
|
-
<desc id="desc">
|
|
4
|
-
|
|
5
|
-
<rect
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
<
|
|
3
|
+
<desc id="desc">BetterCall improves granite4.1:3b tool-call accuracy from 73.4 percent raw to 83.8 percent on BFCL v4 remote Ollama runs.</desc>
|
|
4
|
+
|
|
5
|
+
<rect width="760" height="330" rx="18" fill="#0f172a"/>
|
|
6
|
+
<rect x="26" y="26" width="708" height="278" rx="16" fill="#111827" stroke="#334155"/>
|
|
7
|
+
|
|
8
|
+
<text x="54" y="70" fill="#f8fafc" font-family="Inter, Arial, sans-serif" font-size="27" font-weight="800">Tool-call reliability lift</text>
|
|
9
|
+
<text x="54" y="100" fill="#cbd5e1" font-family="Inter, Arial, sans-serif" font-size="14">BFCL v4 remote Ollama · granite4.1:3b · 3,625 cases</text>
|
|
10
|
+
|
|
9
11
|
<g>
|
|
10
|
-
<text x="
|
|
11
|
-
<rect x="
|
|
12
|
-
<
|
|
12
|
+
<text x="54" y="148" fill="#e2e8f0" font-family="Inter, Arial, sans-serif" font-size="17" font-weight="700">Raw model</text>
|
|
13
|
+
<rect x="54" y="164" width="480" height="30" rx="15" fill="#1e293b"/>
|
|
14
|
+
<rect x="54" y="164" width="352" height="30" rx="15" fill="#64748b"/>
|
|
15
|
+
<text x="560" y="188" fill="#f8fafc" font-family="Inter, Arial, sans-serif" font-size="25" font-weight="800">73.4%</text>
|
|
13
16
|
</g>
|
|
17
|
+
|
|
14
18
|
<g>
|
|
15
|
-
<text x="
|
|
16
|
-
<rect x="
|
|
17
|
-
<
|
|
19
|
+
<text x="54" y="226" fill="#e2e8f0" font-family="Inter, Arial, sans-serif" font-size="17" font-weight="700">With BetterCall</text>
|
|
20
|
+
<rect x="54" y="242" width="480" height="30" rx="15" fill="#1e293b"/>
|
|
21
|
+
<rect x="54" y="242" width="402" height="30" rx="15" fill="#22c55e"/>
|
|
22
|
+
<text x="560" y="266" fill="#f8fafc" font-family="Inter, Arial, sans-serif" font-size="25" font-weight="800">83.8%</text>
|
|
18
23
|
</g>
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
<text x="
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
<text x="
|
|
25
|
-
<text x="502" y="216" fill="#cbd5e1" font-family="Inter, Arial, sans-serif" font-size="15">schema drift, raw tool-call-shaped text.</text>
|
|
24
|
+
|
|
25
|
+
<rect x="560" y="204" width="106" height="30" rx="15" fill="#052e16" stroke="#22c55e" stroke-opacity="0.55"/>
|
|
26
|
+
<text x="613" y="225" fill="#bbf7d0" font-family="Inter, Arial, sans-serif" font-size="15" font-weight="800" text-anchor="middle">+10.4pp</text>
|
|
27
|
+
|
|
28
|
+
<line x1="54" y1="288" x2="332" y2="288" stroke="#334155"/>
|
|
29
|
+
<text x="352" y="293" fill="#94a3b8" font-family="Inter, Arial, sans-serif" font-size="13">Validate, repair, then execute.</text>
|
|
26
30
|
</svg>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Case Study: BetterCall Inside a Tool Gateway
|
|
2
|
+
|
|
3
|
+
`stable-harness` uses BetterCall at the tool gateway boundary, not as a replacement for the agent loop.
|
|
4
|
+
|
|
5
|
+
## Boundary
|
|
6
|
+
|
|
7
|
+
- The agent or workflow backend decides that a tool should be called.
|
|
8
|
+
- The runtime gateway owns the visible tool inventory and hidden or blocked tools.
|
|
9
|
+
- BetterCall validates, repairs, or rejects the selected tool call before the real tool executes.
|
|
10
|
+
- The original tool implementation still executes only after the repaired call passes the boundary.
|
|
11
|
+
|
|
12
|
+
## Flow
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
sequenceDiagram
|
|
16
|
+
participant Agent as Agent backend
|
|
17
|
+
participant Gateway as Tool gateway
|
|
18
|
+
participant BC as BetterCall
|
|
19
|
+
participant Tool as Real tool
|
|
20
|
+
|
|
21
|
+
Agent->>Gateway: tool id + args
|
|
22
|
+
Gateway->>BC: visible tools + candidate call
|
|
23
|
+
BC-->>Gateway: accepted, repaired, or rejected
|
|
24
|
+
alt accepted or repaired
|
|
25
|
+
Gateway->>Tool: execute safe args
|
|
26
|
+
Tool-->>Gateway: result
|
|
27
|
+
else rejected
|
|
28
|
+
Gateway-->>Agent: structured validation error
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Why This Matters
|
|
33
|
+
|
|
34
|
+
The gateway can keep runtime policy and authorization local while BetterCall handles tool-call contract reliability:
|
|
35
|
+
|
|
36
|
+
- wrong or unavailable tool names
|
|
37
|
+
- malformed arguments
|
|
38
|
+
- schema drift
|
|
39
|
+
- enum and type errors
|
|
40
|
+
- raw tool-call-shaped text
|
|
41
|
+
|
|
42
|
+
This keeps BetterCall small: it is the reliability layer inside an agent runtime, not another agent framework.
|
package/docs/faq.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# BetterCall FAQ
|
|
2
|
+
|
|
3
|
+
## When should I use BetterCall?
|
|
4
|
+
|
|
5
|
+
Use BetterCall when your model already emits tool calls, but those calls sometimes fail before execution because of wrong tool names, malformed arguments, schema drift, invalid enum values, extra fields, or raw tool-call-shaped text.
|
|
6
|
+
|
|
7
|
+
## When should I not use BetterCall?
|
|
8
|
+
|
|
9
|
+
Do not use BetterCall as a planning system, memory layer, agent loop, authorization system, or replacement for human approval. Keep those responsibilities in your runtime. BetterCall belongs at the tool-call boundary.
|
|
10
|
+
|
|
11
|
+
## Does BetterCall require a repair model?
|
|
12
|
+
|
|
13
|
+
No. Without `repair` or `repairModel`, BetterCall validates and blocks invalid calls. Add a repair model or callback only when automatic repair is acceptable for your application.
|
|
14
|
+
|
|
15
|
+
## Can BetterCall execute hidden or blocked tools?
|
|
16
|
+
|
|
17
|
+
No. Tool selection repair is constrained by the visible tool inventory you pass in. Hidden tools remain blocked.
|
|
18
|
+
|
|
19
|
+
## Is BetterCall only for LangChain?
|
|
20
|
+
|
|
21
|
+
No. `betterTools(...)` works with LangChain-style tools, but the lower-level APIs work in custom runtimes and tool gateways.
|
|
22
|
+
|
|
23
|
+
## Where should semantic policy live?
|
|
24
|
+
|
|
25
|
+
Keep semantic policy in your tool boundary with `validate(...)` or your gateway. BetterCall can carry the validation result and block or repair around it, but your runtime should own domain policy.
|
|
@@ -12,6 +12,10 @@ The goal is to move developers through three stages:
|
|
|
12
12
|
- [x] Add discoverable GitHub topics: `tool-calling`, `function-calling`, `langchain`, `ollama`, `mcp`, `small-models`, `tool-call-repair`.
|
|
13
13
|
- [x] Put the benchmark lift near the top of the README.
|
|
14
14
|
- [x] Add copyable examples.
|
|
15
|
+
- [x] Add `npx @botbotgo/better-call demo`.
|
|
16
|
+
- [x] Add focused recipes for common integration problems.
|
|
17
|
+
- [x] Add a stable-harness gateway case study.
|
|
18
|
+
- [x] Add an adapter request issue template.
|
|
15
19
|
- [ ] Add a short GIF or terminal recording showing before and after repair.
|
|
16
20
|
- [ ] Add a `good first issue` label set for example adapters and docs.
|
|
17
21
|
|
|
@@ -19,6 +23,7 @@ The goal is to move developers through three stages:
|
|
|
19
23
|
|
|
20
24
|
- [x] Improve package description and keywords.
|
|
21
25
|
- [x] Include examples and README assets in the package allowlist.
|
|
26
|
+
- [x] Add a package binary for the built-in demo.
|
|
22
27
|
- [ ] Publish the next patch version so npm metadata and tarball include the new assets.
|
|
23
28
|
- [ ] Verify the npm package page renders the README image and examples.
|
|
24
29
|
|
|
@@ -44,16 +49,17 @@ The goal is to move developers through three stages:
|
|
|
44
49
|
- [x] Add LangChain-style example.
|
|
45
50
|
- [x] Add Ollama small-model example.
|
|
46
51
|
- [x] Add custom gateway repair example.
|
|
47
|
-
- [
|
|
48
|
-
- [
|
|
49
|
-
- [
|
|
52
|
+
- [x] Add MCP server/client gateway example.
|
|
53
|
+
- [x] Add LangChain adapter helper example.
|
|
54
|
+
- [x] Add OpenAI-compatible local model example.
|
|
55
|
+
- [x] Add LangGraph/stable-harness integration note when stable-harness is the public case study.
|
|
50
56
|
|
|
51
57
|
## Conversion
|
|
52
58
|
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
55
|
-
- [
|
|
56
|
-
- [
|
|
59
|
+
- [x] Add a one-command reproduction path for the smallest local demo.
|
|
60
|
+
- [x] Add a "When should I use BetterCall?" FAQ.
|
|
61
|
+
- [x] Add a "When should I not use BetterCall?" FAQ to build trust.
|
|
62
|
+
- [x] Add a simple issue template for adapter requests.
|
|
57
63
|
- [ ] Track install/download/star movement after each post.
|
|
58
64
|
|
|
59
65
|
## Launch Sequence
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# BetterCall Recipes
|
|
2
|
+
|
|
3
|
+
Each recipe solves one integration problem with a small copyable pattern.
|
|
4
|
+
|
|
5
|
+
- `wrong-tool-name.md`: Repair a tool name before gateway dispatch.
|
|
6
|
+
- `zod-schema.md`: Use Zod object schemas or Zod-shaped records.
|
|
7
|
+
- `validate-only.md`: Run without a repair model and block invalid calls.
|
|
8
|
+
- `ollama-repair.md`: Use a local Ollama model as the repair callback.
|
|
9
|
+
- `semantic-validator.md`: Keep domain validation in your tool boundary.
|
|
10
|
+
|
|
11
|
+
See also `../faq.md` for adoption and boundary guidance.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Recipe: Use Ollama as the Repair Model
|
|
2
|
+
|
|
3
|
+
Use `reliableToolCalls(...)` when you want full control over the repair callback.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { reliableToolCalls } from "@botbotgo/better-call";
|
|
7
|
+
|
|
8
|
+
const result = await reliableToolCalls({
|
|
9
|
+
userInput,
|
|
10
|
+
tools,
|
|
11
|
+
calls,
|
|
12
|
+
repair: async ({ userInput, tools, calls, issues }) => {
|
|
13
|
+
const prompt = [
|
|
14
|
+
"Return corrected JSON only as {\"calls\":[{\"tool\":\"name\",\"args\":{}}]}.",
|
|
15
|
+
`User: ${userInput}`,
|
|
16
|
+
`Tools: ${JSON.stringify(tools)}`,
|
|
17
|
+
`Rejected calls: ${JSON.stringify(calls)}`,
|
|
18
|
+
`Issues: ${JSON.stringify(issues)}`,
|
|
19
|
+
].join("\n");
|
|
20
|
+
|
|
21
|
+
const response = await fetch("http://127.0.0.1:11434/api/generate", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "content-type": "application/json" },
|
|
24
|
+
body: JSON.stringify({ model: "granite4.1:3b", prompt, stream: false, format: "json" }),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const json = await response.json();
|
|
28
|
+
return JSON.parse(json.response).calls;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
See `examples/ollama-small-model.mjs` for a runnable version.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Recipe: Add Semantic Validation
|
|
2
|
+
|
|
3
|
+
Schema validation checks shape. Semantic validation checks whether an argument is allowed in your runtime.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { betterTools } from "@botbotgo/better-call";
|
|
7
|
+
|
|
8
|
+
const [safeTool] = betterTools([{
|
|
9
|
+
name: "deploy",
|
|
10
|
+
schema: deploySchema,
|
|
11
|
+
validate: ({ call }) => {
|
|
12
|
+
if (call.args.environment === "prod") {
|
|
13
|
+
return [{
|
|
14
|
+
kind: "semantic",
|
|
15
|
+
path: "$.args.environment",
|
|
16
|
+
message: "production deploys require a separate approval path",
|
|
17
|
+
}];
|
|
18
|
+
}
|
|
19
|
+
return [];
|
|
20
|
+
},
|
|
21
|
+
invoke: deploy,
|
|
22
|
+
}]);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Keep business policy in `validate(...)`; keep model repair constrained to calls that still pass the tool boundary.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Recipe: Validate and Block Only
|
|
2
|
+
|
|
3
|
+
You do not need a repair model. Without `repair` or `repairModel`, BetterCall validates the call and blocks invalid execution.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { betterTools } from "@botbotgo/better-call";
|
|
7
|
+
|
|
8
|
+
const [safeTool] = betterTools([tool]);
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await safeTool.invoke(modelArgs);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(error.issues);
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use this mode when the cost of a wrong repair is higher than the cost of asking the model or user to try again.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Recipe: Repair a Wrong Tool Name
|
|
2
|
+
|
|
3
|
+
Use `repairToolCall(...)` before a custom runtime, gateway, or MCP-style dispatcher executes a tool.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { repairToolCall } from "@botbotgo/better-call";
|
|
7
|
+
|
|
8
|
+
const repaired = await repairToolCall({
|
|
9
|
+
userInput: "Research the current market.",
|
|
10
|
+
visibleTools: [taskTool],
|
|
11
|
+
hiddenTools: [shellTool],
|
|
12
|
+
invalidToolName: "research",
|
|
13
|
+
args: {
|
|
14
|
+
subagent_type: "research",
|
|
15
|
+
description: "Research the current market.",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (repaired.status === "repaired") {
|
|
20
|
+
await dispatch(repaired.toolName, repaired.args);
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
BetterCall only repairs into the visible tool inventory. Hidden tools remain blocked.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Recipe: Use Zod Schemas
|
|
2
|
+
|
|
3
|
+
BetterCall accepts JSON Schema, Zod object schemas, and Zod-shaped records.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { betterTools } from "@botbotgo/better-call";
|
|
8
|
+
|
|
9
|
+
const tools = betterTools([{
|
|
10
|
+
name: "stock_quote",
|
|
11
|
+
schema: z.object({
|
|
12
|
+
ticker: z.string(),
|
|
13
|
+
market: z.enum(["US", "HK", "CN"]),
|
|
14
|
+
}),
|
|
15
|
+
invoke: async (args) => getStockQuote(args),
|
|
16
|
+
}]);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The same schema normalization is used by the lower-level `reliableToolCalls(...)` and `guardToolCalls(...)` APIs.
|
package/examples/README.md
CHANGED
|
@@ -7,6 +7,9 @@ Each example is intentionally small enough to copy into an app.
|
|
|
7
7
|
- `langchain-basic.mjs`: Wrap a LangChain-style tool and repair invalid args before execution.
|
|
8
8
|
- `ollama-small-model.mjs`: Use an Ollama-compatible repair model callback with `reliableToolCalls`.
|
|
9
9
|
- `gateway-repair.mjs`: Repair an invalid tool name at a custom runtime or MCP gateway boundary.
|
|
10
|
+
- `adapters/langchain-tool-wrapper.mjs`: Tiny adapter helper for LangChain-style tools.
|
|
11
|
+
- `adapters/mcp-gateway-boundary.mjs`: Gateway-boundary pattern for MCP-style tool dispatch.
|
|
12
|
+
- `adapters/openai-compatible-repair.mjs`: Repair callback pattern for OpenAI-compatible local endpoints.
|
|
10
13
|
|
|
11
14
|
## Run
|
|
12
15
|
|
|
@@ -22,10 +25,13 @@ Then run an example:
|
|
|
22
25
|
```bash
|
|
23
26
|
node examples/langchain-basic.mjs
|
|
24
27
|
node examples/gateway-repair.mjs
|
|
28
|
+
node examples/adapters/langchain-tool-wrapper.mjs
|
|
29
|
+
node examples/adapters/mcp-gateway-boundary.mjs
|
|
25
30
|
```
|
|
26
31
|
|
|
27
|
-
The Ollama
|
|
32
|
+
The Ollama and OpenAI-compatible examples expect local model endpoints:
|
|
28
33
|
|
|
29
34
|
```bash
|
|
30
35
|
OLLAMA_BASE_URL=http://127.0.0.1:11434 OLLAMA_MODEL=granite4.1:3b node examples/ollama-small-model.mjs
|
|
36
|
+
OPENAI_BASE_URL=http://127.0.0.1:8000/v1 OPENAI_MODEL=local-model node examples/adapters/openai-compatible-repair.mjs
|
|
31
37
|
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { betterTools } from "../../dist/index.js";
|
|
2
|
+
|
|
3
|
+
export function withBetterCallLangChainTools(tools, options = {}) {
|
|
4
|
+
return betterTools(tools, options);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const searchTool = {
|
|
8
|
+
name: "search",
|
|
9
|
+
description: "Search docs.",
|
|
10
|
+
schema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
query: { type: "string" },
|
|
14
|
+
limit: { type: "integer", minimum: 1, maximum: 5 },
|
|
15
|
+
},
|
|
16
|
+
required: ["query"],
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
async invoke(args) {
|
|
20
|
+
return { searched: args };
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const [search] = withBetterCallLangChainTools([searchTool], {
|
|
25
|
+
userInput: "Search BetterCall docs.",
|
|
26
|
+
repairPolicy: {
|
|
27
|
+
allowCoercion: true,
|
|
28
|
+
allowClamp: true,
|
|
29
|
+
allowDropUnknownKeys: true,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await search.invoke({
|
|
34
|
+
query: "BetterCall tool repair",
|
|
35
|
+
limit: "50",
|
|
36
|
+
debug: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
console.log(JSON.stringify(result, null, 2));
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { repairToolCall } from "../../dist/index.js";
|
|
2
|
+
|
|
3
|
+
const tools = [{
|
|
4
|
+
name: "docs.search",
|
|
5
|
+
description: "Search project documentation.",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
query: { type: "string" },
|
|
10
|
+
max_results: { type: "integer", minimum: 1, maximum: 10 },
|
|
11
|
+
},
|
|
12
|
+
required: ["query"],
|
|
13
|
+
additionalProperties: false,
|
|
14
|
+
},
|
|
15
|
+
}];
|
|
16
|
+
|
|
17
|
+
async function dispatchMcpToolCall(request) {
|
|
18
|
+
const repaired = await repairToolCall({
|
|
19
|
+
userInput: request.userInput,
|
|
20
|
+
visibleTools: tools,
|
|
21
|
+
invalidToolName: request.name,
|
|
22
|
+
args: request.arguments,
|
|
23
|
+
repairPolicy: {
|
|
24
|
+
allowCoercion: true,
|
|
25
|
+
allowClamp: true,
|
|
26
|
+
allowDropUnknownKeys: true,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (repaired.status === "rejected") {
|
|
31
|
+
return { error: repaired.reason, violations: repaired.violations };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: repaired.toolName,
|
|
36
|
+
arguments: repaired.args,
|
|
37
|
+
executed: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const response = await dispatchMcpToolCall({
|
|
42
|
+
userInput: "Search the docs for BetterCall examples.",
|
|
43
|
+
name: "search_docs",
|
|
44
|
+
arguments: {
|
|
45
|
+
query: "BetterCall examples",
|
|
46
|
+
max_results: "50",
|
|
47
|
+
debug: true,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log(JSON.stringify(response, null, 2));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { reliableToolCalls } from "../../dist/index.js";
|
|
2
|
+
|
|
3
|
+
const baseUrl = process.env.OPENAI_BASE_URL ?? "http://127.0.0.1:8000/v1";
|
|
4
|
+
const model = process.env.OPENAI_MODEL ?? "local-model";
|
|
5
|
+
const apiKey = process.env.OPENAI_API_KEY ?? "not-needed-for-local";
|
|
6
|
+
|
|
7
|
+
const tools = [{
|
|
8
|
+
name: "stock_quote",
|
|
9
|
+
description: "Get a stock quote.",
|
|
10
|
+
schema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
ticker: { type: "string" },
|
|
14
|
+
market: { type: "string", enum: ["US", "HK", "CN"] },
|
|
15
|
+
},
|
|
16
|
+
required: ["ticker", "market"],
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
}];
|
|
20
|
+
|
|
21
|
+
const result = await reliableToolCalls({
|
|
22
|
+
userInput: "Get Apple stock in the US market.",
|
|
23
|
+
tools,
|
|
24
|
+
calls: [{ tool: "stock_price", args: { symbol: "Apple", market: "NASDAQ" } }],
|
|
25
|
+
repair: async ({ userInput, tools, calls, issues }) => {
|
|
26
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"authorization": `Bearer ${apiKey}`,
|
|
30
|
+
"content-type": "application/json",
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
model,
|
|
34
|
+
response_format: { type: "json_object" },
|
|
35
|
+
messages: [{
|
|
36
|
+
role: "user",
|
|
37
|
+
content: [
|
|
38
|
+
"Return corrected JSON only as {\"calls\":[{\"tool\":\"name\",\"args\":{}}]}.",
|
|
39
|
+
`User: ${userInput}`,
|
|
40
|
+
`Tools: ${JSON.stringify(tools)}`,
|
|
41
|
+
`Rejected calls: ${JSON.stringify(calls)}`,
|
|
42
|
+
`Issues: ${JSON.stringify(issues)}`,
|
|
43
|
+
].join("\n"),
|
|
44
|
+
}],
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) throw new Error(`OpenAI-compatible request failed: ${response.status}`);
|
|
48
|
+
const json = await response.json();
|
|
49
|
+
return JSON.parse(json.choices[0].message.content).calls;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(JSON.stringify({ model, result }, null, 2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botbotgo/better-call",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "Small-model tool-call reliability layer for LangChain and custom agent runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
"import": "./dist/index.js"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
+
"bin": {
|
|
37
|
+
"better-call": "./scripts/demo.mjs"
|
|
38
|
+
},
|
|
36
39
|
"files": [
|
|
37
40
|
"dist",
|
|
38
41
|
"benchmarks",
|
|
@@ -40,7 +43,10 @@
|
|
|
40
43
|
"examples",
|
|
41
44
|
"docs/banner.svg",
|
|
42
45
|
"docs/benchmark-lift.svg",
|
|
46
|
+
"docs/faq.md",
|
|
47
|
+
"docs/case-studies",
|
|
43
48
|
"docs/marketing",
|
|
49
|
+
"docs/recipes",
|
|
44
50
|
"README.md",
|
|
45
51
|
"NOTICE",
|
|
46
52
|
"LICENSE"
|
package/scripts/demo.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { betterTools, repairToolCall, reliableToolCalls } from "../dist/index.js";
|
|
3
|
+
|
|
4
|
+
const json = process.argv.includes("--json");
|
|
5
|
+
|
|
6
|
+
const stockQuote = {
|
|
7
|
+
name: "stock_quote",
|
|
8
|
+
description: "Get a stock quote.",
|
|
9
|
+
schema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
ticker: { type: "string" },
|
|
13
|
+
market: { type: "string", enum: ["US", "HK", "CN"] },
|
|
14
|
+
},
|
|
15
|
+
required: ["ticker", "market"],
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
},
|
|
18
|
+
async invoke(args) {
|
|
19
|
+
return { quote: args };
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const badArgs = { symbol: "Apple", market: "NASDAQ" };
|
|
24
|
+
|
|
25
|
+
const [wrappedStockQuote] = betterTools([stockQuote], {
|
|
26
|
+
userInput: "Get Apple stock in the US market.",
|
|
27
|
+
repair() {
|
|
28
|
+
return [{ tool: "stock_quote", args: { ticker: "AAPL", market: "US" } }];
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const wrappedOutput = await wrappedStockQuote.invoke(badArgs);
|
|
33
|
+
|
|
34
|
+
const reliableResult = await reliableToolCalls({
|
|
35
|
+
userInput: "Get Apple stock in the US market.",
|
|
36
|
+
tools: [stockQuote],
|
|
37
|
+
calls: [{ tool: "stock_price", args: badArgs }],
|
|
38
|
+
repair() {
|
|
39
|
+
return [{ tool: "stock_quote", args: { ticker: "AAPL", market: "US" } }];
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const gatewayResult = await repairToolCall({
|
|
44
|
+
userInput: "Research the current market.",
|
|
45
|
+
visibleTools: [{
|
|
46
|
+
name: "task",
|
|
47
|
+
description: "Delegate a task to a visible specialist.",
|
|
48
|
+
schema: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
subagent_type: { type: "string", enum: ["research", "ops"] },
|
|
52
|
+
description: { type: "string" },
|
|
53
|
+
},
|
|
54
|
+
required: ["subagent_type", "description"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
57
|
+
}],
|
|
58
|
+
invalidToolName: "research",
|
|
59
|
+
args: {
|
|
60
|
+
subagent_type: "research",
|
|
61
|
+
description: "Research the current market.",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const report = {
|
|
66
|
+
wrappedTool: {
|
|
67
|
+
before: badArgs,
|
|
68
|
+
after: wrappedOutput,
|
|
69
|
+
},
|
|
70
|
+
reliableToolCalls: {
|
|
71
|
+
before: { tool: "stock_price", args: badArgs },
|
|
72
|
+
repaired: reliableResult.calls[0],
|
|
73
|
+
diagnostics: reliableResult.diagnostics,
|
|
74
|
+
},
|
|
75
|
+
gatewayRepair: gatewayResult,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (json) {
|
|
79
|
+
console.log(JSON.stringify(report, null, 2));
|
|
80
|
+
} else {
|
|
81
|
+
console.log("BetterCall demo");
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log("1. Wrapped tool args");
|
|
84
|
+
console.log(` before: ${JSON.stringify(report.wrappedTool.before)}`);
|
|
85
|
+
console.log(` after: ${JSON.stringify(report.wrappedTool.after)}`);
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log("2. Tool-call repair");
|
|
88
|
+
console.log(` before: ${JSON.stringify(report.reliableToolCalls.before)}`);
|
|
89
|
+
console.log(` after: ${JSON.stringify(report.reliableToolCalls.repaired)}`);
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log("3. Gateway tool-name repair");
|
|
92
|
+
console.log(` status: ${report.gatewayRepair.status}`);
|
|
93
|
+
console.log(` tool: ${report.gatewayRepair.originalToolName} -> ${report.gatewayRepair.toolName}`);
|
|
94
|
+
console.log(` reason: ${report.gatewayRepair.reason}`);
|
|
95
|
+
console.log("");
|
|
96
|
+
console.log("Run with --json for the full structured result.");
|
|
97
|
+
}
|