@botbotgo/better-call 0.1.23 → 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 +31 -1
- package/docs/benchmark-lift.svg +30 -0
- package/docs/case-studies/stable-harness-tool-gateway.md +42 -0
- package/docs/faq.md +25 -0
- package/docs/marketing/growth-checklist.md +73 -0
- package/docs/marketing/launch-post.md +50 -0
- package/docs/marketing/positioning.md +43 -0
- 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 +37 -0
- 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/examples/gateway-repair.mjs +25 -0
- package/examples/langchain-basic.mjs +30 -0
- package/examples/ollama-small-model.mjs +46 -0
- package/package.json +19 -3
- package/scripts/demo.mjs +97 -0
package/README.md
CHANGED
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://github.com/botbotgo/better-call/actions/workflows/release.yml)
|
|
10
10
|
|
|
11
|
-
BetterCall is a small
|
|
11
|
+
BetterCall is a small-model tool-call reliability layer for LangChain and custom agent runtimes.
|
|
12
12
|
|
|
13
13
|
Wrap your existing tools, validate model-generated calls before execution, and optionally ask a repair model to fix rejected calls. In full BFCL v4 remote Ollama runs, the best measured lift was `granite4.1:3b`: **73.4% raw -> 83.8% with BetterCall**.
|
|
14
14
|
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="docs/benchmark-lift.svg" alt="BetterCall improved granite4.1:3b tool-call accuracy from 73.4% to 83.8% on BFCL v4 remote Ollama runs">
|
|
17
|
+
</p>
|
|
18
|
+
|
|
15
19
|
```ts
|
|
16
20
|
import { betterTools } from "@botbotgo/better-call";
|
|
17
21
|
|
|
@@ -20,6 +24,12 @@ const tools = betterTools([searchTool, calculatorTool]);
|
|
|
20
24
|
|
|
21
25
|
No model means validate and block only. Add `repairModel` when you want automatic repair.
|
|
22
26
|
|
|
27
|
+
Try the built-in demo after installing:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @botbotgo/better-call demo
|
|
31
|
+
```
|
|
32
|
+
|
|
23
33
|
## Why BetterCall?
|
|
24
34
|
|
|
25
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.
|
|
@@ -30,6 +40,17 @@ Small models often know that a tool is needed but still fail at the boundary: wr
|
|
|
30
40
|
- Repair rejected calls with a LangChain chat model or custom callback.
|
|
31
41
|
- Keep lower-level primitives available for custom runtimes and gateways.
|
|
32
42
|
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
The `examples/` directory has copyable starting points:
|
|
46
|
+
|
|
47
|
+
- `examples/langchain-basic.mjs`: wrap a LangChain-style tool and repair bad args.
|
|
48
|
+
- `examples/ollama-small-model.mjs`: use an Ollama repair callback for a small local model.
|
|
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.
|
|
53
|
+
|
|
33
54
|
## Installation
|
|
34
55
|
|
|
35
56
|
```bash
|
|
@@ -230,6 +251,15 @@ Latest completed remote run artifact: `benchmarks/bfcl-real-remote-completed-sum
|
|
|
230
251
|
| `qwen3.5:4b` | 3,625 | 43.6% | 43.4% | -0.2pp | 1,847 |
|
|
231
252
|
| `gemma4:e2b` | 3,625 | 24.3% | 24.7% | +0.4pp | 2,641 |
|
|
232
253
|
|
|
254
|
+
## Positioning and Launch Assets
|
|
255
|
+
|
|
256
|
+
- `docs/marketing/positioning.md`: product positioning, audience, differentiators, and calls to action.
|
|
257
|
+
- `docs/marketing/launch-post.md`: launch-post draft and short social version.
|
|
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.
|
|
262
|
+
|
|
233
263
|
## Requirements
|
|
234
264
|
|
|
235
265
|
BetterCall is an ESM package for modern Node.js runtimes. It has one runtime dependency: `zod`.
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
<title id="title">BetterCall BFCL small-model lift</title>
|
|
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
|
+
|
|
11
|
+
<g>
|
|
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>
|
|
16
|
+
</g>
|
|
17
|
+
|
|
18
|
+
<g>
|
|
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>
|
|
23
|
+
</g>
|
|
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>
|
|
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.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# BetterCall Growth Checklist
|
|
2
|
+
|
|
3
|
+
The goal is to move developers through three stages:
|
|
4
|
+
|
|
5
|
+
1. Notice the problem.
|
|
6
|
+
2. Run a small example.
|
|
7
|
+
3. Keep BetterCall in their runtime or tool gateway.
|
|
8
|
+
|
|
9
|
+
## Repository
|
|
10
|
+
|
|
11
|
+
- [x] Use a precise GitHub description.
|
|
12
|
+
- [x] Add discoverable GitHub topics: `tool-calling`, `function-calling`, `langchain`, `ollama`, `mcp`, `small-models`, `tool-call-repair`.
|
|
13
|
+
- [x] Put the benchmark lift near the top of the README.
|
|
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.
|
|
19
|
+
- [ ] Add a short GIF or terminal recording showing before and after repair.
|
|
20
|
+
- [ ] Add a `good first issue` label set for example adapters and docs.
|
|
21
|
+
|
|
22
|
+
## npm
|
|
23
|
+
|
|
24
|
+
- [x] Improve package description and keywords.
|
|
25
|
+
- [x] Include examples and README assets in the package allowlist.
|
|
26
|
+
- [x] Add a package binary for the built-in demo.
|
|
27
|
+
- [ ] Publish the next patch version so npm metadata and tarball include the new assets.
|
|
28
|
+
- [ ] Verify the npm package page renders the README image and examples.
|
|
29
|
+
|
|
30
|
+
## Content
|
|
31
|
+
|
|
32
|
+
- [x] Write launch-post draft.
|
|
33
|
+
- [x] Write short social version.
|
|
34
|
+
- [ ] Publish a technical post using the launch draft.
|
|
35
|
+
- [ ] Publish a benchmark-focused post explaining the BFCL wrapper methodology.
|
|
36
|
+
- [ ] Publish a comparison post: BetterCall vs structured-output retries vs guardrails.
|
|
37
|
+
|
|
38
|
+
## Developer Communities
|
|
39
|
+
|
|
40
|
+
- [ ] Hacker News: launch with the problem-first title, not a marketing title.
|
|
41
|
+
- [ ] Reddit: post to relevant LLM engineering communities with the benchmark and example.
|
|
42
|
+
- [ ] X/LinkedIn: short thread with before/after tool call examples.
|
|
43
|
+
- [ ] LangChain community: share the `betterTools(...)` example.
|
|
44
|
+
- [ ] Ollama/local-model community: share the BFCL small-model result.
|
|
45
|
+
- [ ] MCP community: share the gateway repair example.
|
|
46
|
+
|
|
47
|
+
## Ecosystem
|
|
48
|
+
|
|
49
|
+
- [x] Add LangChain-style example.
|
|
50
|
+
- [x] Add Ollama small-model example.
|
|
51
|
+
- [x] Add custom gateway repair example.
|
|
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.
|
|
56
|
+
|
|
57
|
+
## Conversion
|
|
58
|
+
|
|
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.
|
|
63
|
+
- [ ] Track install/download/star movement after each post.
|
|
64
|
+
|
|
65
|
+
## Launch Sequence
|
|
66
|
+
|
|
67
|
+
1. Publish the next patch version with the README/assets/examples.
|
|
68
|
+
2. Verify GitHub and npm pages.
|
|
69
|
+
3. Post the short social version.
|
|
70
|
+
4. Post the technical launch article.
|
|
71
|
+
5. Share the three examples to the matching communities.
|
|
72
|
+
6. Collect adapter requests as issues.
|
|
73
|
+
7. Build the top requested adapter/example first.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Launch Post Draft
|
|
2
|
+
|
|
3
|
+
## Title Options
|
|
4
|
+
|
|
5
|
+
- Small models can choose the right tool and still break at the execution boundary
|
|
6
|
+
- BetterCall: a tiny reliability layer for LLM tool calls
|
|
7
|
+
- Fix malformed LLM tool calls before they execute
|
|
8
|
+
|
|
9
|
+
## Post
|
|
10
|
+
|
|
11
|
+
LLM tool calling has a boring failure mode that shows up constantly with small models:
|
|
12
|
+
|
|
13
|
+
The model knows a tool is needed, but the actual call is not executable.
|
|
14
|
+
|
|
15
|
+
It may pick a near-miss tool name, send `symbol` instead of `ticker`, use an enum value the schema rejects, include extra fields, or emit raw tool-call-shaped text. If your runtime sends that straight to a real tool, the best case is a failed request. The worse case is the wrong operation reaching production code.
|
|
16
|
+
|
|
17
|
+
BetterCall is a small TypeScript reliability layer for that boundary.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { betterTools } from "@botbotgo/better-call";
|
|
21
|
+
|
|
22
|
+
const tools = betterTools([searchTool, calculatorTool], {
|
|
23
|
+
repairModel,
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It wraps existing LangChain-style tools, validates model-generated calls before execution, optionally asks a repair model or custom callback to fix rejected calls, validates again, and only then invokes the original tool.
|
|
28
|
+
|
|
29
|
+
The lower-level API also works at a custom runtime or MCP gateway boundary:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { repairToolCall } from "@botbotgo/better-call";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
In full BFCL v4 remote Ollama runs, the best measured lift was `granite4.1:3b`: `73.4%` raw to `83.8%` with BetterCall across `3,625` cases.
|
|
36
|
+
|
|
37
|
+
BetterCall is not an agent framework. It is the small reliability layer inside one.
|
|
38
|
+
|
|
39
|
+
Repo: https://github.com/botbotgo/better-call
|
|
40
|
+
Package: https://www.npmjs.com/package/@botbotgo/better-call
|
|
41
|
+
|
|
42
|
+
## Short Social Version
|
|
43
|
+
|
|
44
|
+
Small models often know which tool they need, then fail at the execution boundary: wrong tool name, malformed args, schema drift, extra fields, raw tool-call-shaped text.
|
|
45
|
+
|
|
46
|
+
BetterCall wraps existing tools and validates, repairs, or rejects calls before real execution.
|
|
47
|
+
|
|
48
|
+
`granite4.1:3b`: `73.4%` raw to `83.8%` with BetterCall on BFCL v4 remote Ollama runs.
|
|
49
|
+
|
|
50
|
+
https://github.com/botbotgo/better-call
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# BetterCall Positioning
|
|
2
|
+
|
|
3
|
+
## One-line Positioning
|
|
4
|
+
|
|
5
|
+
BetterCall is a small-model tool-call reliability layer for LangChain and custom agent runtimes.
|
|
6
|
+
|
|
7
|
+
## Short Pitch
|
|
8
|
+
|
|
9
|
+
Small models often know that a tool is needed but fail at the execution boundary: wrong tool names, malformed arguments, schema drift, extra fields, or raw tool-call-shaped text. BetterCall wraps existing tools and validates, repairs, or rejects the call before the real tool runs.
|
|
10
|
+
|
|
11
|
+
## Primary Audience
|
|
12
|
+
|
|
13
|
+
- Developers building LangChain JS or custom TypeScript agent runtimes.
|
|
14
|
+
- Teams using small local models through Ollama or OpenAI-compatible endpoints.
|
|
15
|
+
- Runtime authors who need a tool gateway boundary, not another full agent framework.
|
|
16
|
+
- MCP or tool-server authors who need schema-aware call repair before execution.
|
|
17
|
+
|
|
18
|
+
## Differentiation
|
|
19
|
+
|
|
20
|
+
| Alternative | What it usually solves | BetterCall position |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Agent frameworks | Agent loop, memory, planning, tool invocation | Drop-in reliability layer at the tool boundary |
|
|
23
|
+
| Guardrails frameworks | Input/output safety and policy checks | Tool-call contract validation and repair |
|
|
24
|
+
| Structured-output libraries | JSON/object extraction and retries | Existing tool-call repair before execution |
|
|
25
|
+
| MCP platforms | Hosted API/tool surfaces | Independent npm package for any JS runtime |
|
|
26
|
+
|
|
27
|
+
## Proof Point
|
|
28
|
+
|
|
29
|
+
In full BFCL v4 remote Ollama runs, the best measured lift was `granite4.1:3b`: `73.4%` raw to `83.8%` with BetterCall across `3,625` cases.
|
|
30
|
+
|
|
31
|
+
## Core Messages
|
|
32
|
+
|
|
33
|
+
- Small models can pick the right intent and still break at the tool boundary.
|
|
34
|
+
- BetterCall validates and repairs before execution, so unsafe or malformed calls do not reach real tools.
|
|
35
|
+
- It works with existing LangChain-style tools and lower-level custom gateways.
|
|
36
|
+
- It is not an agent framework; it is the reliability layer inside one.
|
|
37
|
+
|
|
38
|
+
## Calls To Action
|
|
39
|
+
|
|
40
|
+
- Install: `npm install @botbotgo/better-call`
|
|
41
|
+
- Try the 30-line examples in `examples/`
|
|
42
|
+
- Run `npm run bench:bfcl` to reproduce the wrapper sanity benchmark
|
|
43
|
+
- Use `repairToolCall(...)` at a runtime or MCP gateway boundary
|
|
@@ -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.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# BetterCall Examples
|
|
2
|
+
|
|
3
|
+
Each example is intentionally small enough to copy into an app.
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
- `langchain-basic.mjs`: Wrap a LangChain-style tool and repair invalid args before execution.
|
|
8
|
+
- `ollama-small-model.mjs`: Use an Ollama-compatible repair model callback with `reliableToolCalls`.
|
|
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.
|
|
13
|
+
|
|
14
|
+
## Run
|
|
15
|
+
|
|
16
|
+
Build the package first:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then run an example:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node examples/langchain-basic.mjs
|
|
27
|
+
node examples/gateway-repair.mjs
|
|
28
|
+
node examples/adapters/langchain-tool-wrapper.mjs
|
|
29
|
+
node examples/adapters/mcp-gateway-boundary.mjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The Ollama and OpenAI-compatible examples expect local model endpoints:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
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
|
|
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));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { repairToolCall } from "../dist/index.js";
|
|
2
|
+
|
|
3
|
+
const visibleTools = [{
|
|
4
|
+
name: "task",
|
|
5
|
+
description: "Delegate a task to a visible specialist.",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
subagent_type: { type: "string", enum: ["research", "ops"] },
|
|
10
|
+
description: { type: "string" },
|
|
11
|
+
},
|
|
12
|
+
required: ["subagent_type", "description"],
|
|
13
|
+
additionalProperties: false,
|
|
14
|
+
},
|
|
15
|
+
}];
|
|
16
|
+
|
|
17
|
+
const result = await repairToolCall({
|
|
18
|
+
userInput: "Research the current market.",
|
|
19
|
+
visibleTools,
|
|
20
|
+
hiddenTools: [{ name: "shell" }],
|
|
21
|
+
invalidToolName: "research",
|
|
22
|
+
args: { subagent_type: "research", description: "Research the current market." },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.log(JSON.stringify(result, null, 2));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { betterTools } from "../dist/index.js";
|
|
2
|
+
|
|
3
|
+
const stockQuote = {
|
|
4
|
+
name: "stock_quote",
|
|
5
|
+
description: "Get a stock quote.",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
ticker: { type: "string" },
|
|
10
|
+
market: { type: "string", enum: ["US", "HK", "CN"] },
|
|
11
|
+
},
|
|
12
|
+
required: ["ticker", "market"],
|
|
13
|
+
additionalProperties: false,
|
|
14
|
+
},
|
|
15
|
+
async invoke(args) {
|
|
16
|
+
return { ok: true, quote: args };
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const [safeStockQuote] = betterTools([stockQuote], {
|
|
21
|
+
userInput: "Get Apple stock in the US market.",
|
|
22
|
+
repair() {
|
|
23
|
+
return [{ tool: "stock_quote", args: { ticker: "AAPL", market: "US" } }];
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const before = { symbol: "Apple", market: "NASDAQ" };
|
|
28
|
+
const after = await safeStockQuote.invoke(before);
|
|
29
|
+
|
|
30
|
+
console.log(JSON.stringify({ before, after }, null, 2));
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { reliableToolCalls } from "../dist/index.js";
|
|
2
|
+
|
|
3
|
+
const baseUrl = process.env.OLLAMA_BASE_URL ?? "http://127.0.0.1:11434";
|
|
4
|
+
const model = process.env.OLLAMA_MODEL ?? "granite4.1:3b";
|
|
5
|
+
|
|
6
|
+
const tools = [{
|
|
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
|
+
}];
|
|
19
|
+
|
|
20
|
+
const badCall = { tool: "stock_price", args: { symbol: "Apple", market: "NASDAQ" } };
|
|
21
|
+
|
|
22
|
+
const result = await reliableToolCalls({
|
|
23
|
+
userInput: "Get Apple stock in the US market.",
|
|
24
|
+
tools,
|
|
25
|
+
calls: [badCall],
|
|
26
|
+
repair: async ({ userInput, tools, calls, issues }) => {
|
|
27
|
+
const prompt = [
|
|
28
|
+
"Return corrected JSON only as {\"calls\":[{\"tool\":\"name\",\"args\":{}}]}.",
|
|
29
|
+
`User: ${userInput}`,
|
|
30
|
+
`Tools: ${JSON.stringify(tools)}`,
|
|
31
|
+
`Rejected calls: ${JSON.stringify(calls)}`,
|
|
32
|
+
`Issues: ${JSON.stringify(issues)}`,
|
|
33
|
+
].join("\n");
|
|
34
|
+
|
|
35
|
+
const response = await fetch(`${baseUrl}/api/generate`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "content-type": "application/json" },
|
|
38
|
+
body: JSON.stringify({ model, prompt, stream: false, format: "json" }),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) throw new Error(`Ollama request failed: ${response.status}`);
|
|
41
|
+
const json = await response.json();
|
|
42
|
+
return JSON.parse(json.response).calls;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log(JSON.stringify({ model, badCall, result }, null, 2));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botbotgo/better-call",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.25",
|
|
4
|
+
"description": "Small-model tool-call reliability layer for LangChain and custom agent runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -18,7 +18,14 @@
|
|
|
18
18
|
"tool-calling",
|
|
19
19
|
"function-calling",
|
|
20
20
|
"guardrails",
|
|
21
|
-
"reliability"
|
|
21
|
+
"reliability",
|
|
22
|
+
"langchain",
|
|
23
|
+
"ollama",
|
|
24
|
+
"mcp",
|
|
25
|
+
"tool-call-repair",
|
|
26
|
+
"function-call-repair",
|
|
27
|
+
"small-models",
|
|
28
|
+
"agent-runtime"
|
|
22
29
|
],
|
|
23
30
|
"exports": {
|
|
24
31
|
".": {
|
|
@@ -26,11 +33,20 @@
|
|
|
26
33
|
"import": "./dist/index.js"
|
|
27
34
|
}
|
|
28
35
|
},
|
|
36
|
+
"bin": {
|
|
37
|
+
"better-call": "./scripts/demo.mjs"
|
|
38
|
+
},
|
|
29
39
|
"files": [
|
|
30
40
|
"dist",
|
|
31
41
|
"benchmarks",
|
|
32
42
|
"scripts",
|
|
43
|
+
"examples",
|
|
33
44
|
"docs/banner.svg",
|
|
45
|
+
"docs/benchmark-lift.svg",
|
|
46
|
+
"docs/faq.md",
|
|
47
|
+
"docs/case-studies",
|
|
48
|
+
"docs/marketing",
|
|
49
|
+
"docs/recipes",
|
|
34
50
|
"README.md",
|
|
35
51
|
"NOTICE",
|
|
36
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
|
+
}
|