@easynet/better-call
v0.1.84
Published
Framework-agnostic tool-call reliability boundary for LLM agents and gateways.
Maintainers
Readme
BetterCall
BetterCall is a framework-agnostic tool-call reliability boundary. It accepts a tool inventory, proposed model tool calls, schemas, and typed policy; it returns validated, repaired, or blocked calls with diagnostics before any real tool executes.
It belongs at the last boundary before tool execution:
model/tool intent -> runtime gateway -> BetterCall -> real toolBetterCall validates, repairs when policy allows it, preserves diagnostics, and blocks invalid calls instead of silently executing them. The key product promise is simple: a proposed call becomes a visible, schema-valid, diagnosable call, or it does not reach the real tool.
BetterCall core -> adapters/gateways -> agent runtimes and products
tool-call reliability -> integration surface -> user-facing workflowsThis is intentionally narrower than an agent framework. Use your own runtime, OpenAI-compatible gateways, MCP servers, Gemini adapters, DeepAgents, or LangChain for planning, memory, approvals, orchestration, and user-facing product workflows. Use BetterCall only at the tool-call boundary where a proposed call must become a visible, valid, diagnosable call or be blocked before execution.
License
BetterCall is licensed under the Apache License, Version 2.0. Copyright 2026 BotBotGo. Please preserve the included license and attribution notices when you use, copy, modify, or distribute this software.
For engineering workflows, this means read-only evidence tools can stay visible while publish, deploy, push, or shell-mutation tools remain hidden or blocked. The runnable DevOps proof is:
npm run build
node examples/devops-gateway-boundary.mjsThe example repairs a near-miss GitHub Actions read call into a visible
read-only evidence tool, rejects a hidden release_publish call, and blocks a
mutating git command through schema validation.
Install
npm install @easynet/better-callQuick Start
At a framework or gateway boundary, repair a proposed call before dispatch and only execute a visible, valid tool:
import { repairToolCall } from "@easynet/better-call";
const result = await repairToolCall({
userInput: "Research the current market.",
visibleTools: [{
name: "task",
description: "Delegate to a visible specialist.",
schema: {
type: "object",
properties: {
subagent_type: { type: "string", enum: ["research", "ops"] },
description: { type: "string" },
},
required: ["subagent_type", "description"],
additionalProperties: false,
},
}],
hiddenTools: [{ name: "shell" }],
invalidToolName: "research",
args: {
subagent_type: "research",
description: "Research the current market.",
},
});
if (result.status !== "repaired" && result.status !== "unchanged") {
throw new Error(`Blocked tool call: ${result.reason}`);
}
await dispatch(result.toolName, result.args);For a validate-only gateway decision, use the packaged gateway helper:
import { decideToolCallGateway } from "@easynet/better-call/gateway";
const decision = await decideToolCallGateway({
userInput,
visibleTools,
hiddenTools,
call: proposedToolCall,
});
if (!decision.executable) {
return { status: "blocked", reason: decision.reason };
}The repair result is structured. Production gateways can log status, reason,
violations, selected toolName, repaired args, and selection diagnostics
before execution.
API Surfaces
Core APIs live at the package root:
guardToolCalls(...): validate proposed calls against visible tools and schemas.repairToolCall(...): repair or reject one proposed call at a gateway boundary.normalizeArgsBySchema(...): apply policy-controlled argument normalization.reliableToolCalls(...): combine guard, repair, and second-pass validation.summarizeBetterCallTelemetry(...): turn decisions into compact telemetry.
Adapter APIs live under explicit subpaths:
@easynet/better-call/gateway: framework-agnostic validate/repair/block decisions for HTTP, MCP, OpenAI-compatible, or custom tool gateways.@easynet/better-call/langchain: LangChain-style tool adapter.
For LangChain-style tools, use the adapter subpath before handing tools to your agent runtime:
import { betterTools } from "@easynet/better-call/langchain";
const tools = betterTools(existingTools);Failure Modes
| Failure mode | BetterCall behavior |
| --- | --- |
| Unknown tool | Repairs only into the visible tool inventory, or rejects the call. Hidden and blocked tools remain unavailable. |
| Malformed args | Parses tool-call-shaped payloads and normalizes arguments against the target schema when policy permits it. |
| Enum mismatch | Repairs to an allowed enum value when the intent is clear, otherwise blocks the call with diagnostics. |
| Extra fields | Removes fields disallowed by additionalProperties: false when policy permits normalization. |
| Missing business facts | Returns a blocked or incomplete result that the runtime can turn into a user question. BetterCall does not invent IDs, prices, credentials, or approvals. |
| Unsafe action | Leaves the product/runtime policy in charge of block, approval, or escalation. BetterCall does not approve destructive actions. |
| Invalid repair | Rejects the repair result instead of executing a call that still violates schema or inventory policy. |
Repair Policy Examples
Use repair policy to keep mechanical fixes separate from business decisions:
- allow call-envelope and schema normalization when the intended call is visible and unambiguous
- allow safe type coercion only when the schema makes the target type obvious
- request more information for missing business facts such as account IDs, customer IDs, dates, or prices
- block or escalate destructive and sensitive calls when intent, authority, or approval is unclear
- preserve diagnostics so downstream runtimes can show the raw call, normalized call, repair decision, validation result, and execution boundary
See examples/repair-policy-catalog.mjs for a runnable policy catalog and
docs/recipes/repair-policy-catalog.md for website-ready copy.
For the read-only engineering evidence pattern, see
docs/recipes/devops-read-only-boundary.md.
Evidence
On a BFCL v4 remote Ollama run with granite4.1:3b over 3,625 cases,
BetterCall improved tool-call accuracy from 73.4% raw model output to 83.8% with
validation and repair. The more useful operating metric is failure reduction:
the failure rate fell from 26.6% to 16.2%, a 10.4 percentage point drop and
about 39% fewer failed tool-call cases.
On a newer qwen3.5:4b partial BFCL checkpoint, the completed
irrelevance category shows the kind of boundary win BetterCall is built for:
failure rate fell from 18.3% raw to 7.5% with BetterCall, a 10.8 percentage
point drop and about 59% fewer failures when the model should not call a tool.
Across the 1,648 completed cases in that interrupted run, failure rate fell from
25.9% to 16.2%, about 37% fewer failures.
The chart artifact is kept in the repository at docs/benchmark-lift.svg.
Reproduce the local proof table and sanity checks with:
npm run bench:proofThat command builds the package, runs the deterministic BetterCall sanity cases,
and prints the checked-in BFCL summary from
benchmarks/bfcl-targeted-summary.json. For a live local-model run, provide
BFCL data and an Ollama endpoint, then run:
BFCL_DATA_DIR=/path/to/bfcl_eval/data \
OLLAMA_BASE_URL=http://127.0.0.1:11434 \
BENCH_MODELS=granite4.1:3b \
npm run bench:bfcl:realBoundary
BetterCall is not a planner, memory layer, authorization system, approval workflow, or agent orchestration framework. Keep those responsibilities in your runtime or control plane.
Use BetterCall for:
- tool-call schema validation
- visible-inventory tool-name repair
- argument normalization
- structured repair diagnostics
- block-on-failure behavior
Keep outside BetterCall:
- planning and task decomposition
- long-term memory
- user or human approval
- authorization and credential policy
- semantic business rules that belong to your tool boundary
Publication Policy
The npm package is publicly installable, but the project source is not open. Published artifacts are limited to minified runtime JavaScript, TypeScript declarations, this README, and required package metadata. Source files, examples, scripts, benchmarks, documentation sources, lockfiles, build config, and source maps are not included in the published package.
