mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(ai): keep root $defs in Anthropic non-strict tool schemas
The non-strict input_schema kept only type/properties/required, so property $refs into root $defs or definitions pointed at nothing. Claude then produced wrong arguments (a JSON string instead of an object), and some Anthropic-compatible gateways rejected the request. Root anyOf/oneOf/allOf are still left out: Anthropic rejects them with a 400. That part of #9557 and #9134 stays open.
This commit is contained in:
parent
9ee7586a95
commit
4a6a58eb59
3 changed files with 36 additions and 1 deletions
|
|
@ -5,6 +5,7 @@
|
|||
### Fixed
|
||||
|
||||
- Fixed OpenCode Go Claude Haiku 5.5 failing with a 400 (`Invalid signature in thinking block ... bound to a different conversation`) after the tool list, system prompt, or earlier history changed, which left the session failing on every retry. OpenCode Go and Zen Claude 5.5 models, Opus 5, and Fable 5.1 now use per-message effort and send `thinking.block_binding: drop_block`, like the Anthropic provider ([#10954](https://github.com/earendil-works/pi/issues/10954))
|
||||
- Fixed Anthropic non-strict tool schemas dropping root `$defs` and `definitions`, which left property `$ref`s pointing at nothing. Claude then produced wrong arguments for such tools (common in MCP servers), for example a JSON string instead of an object, and some Anthropic-compatible gateways rejected the request outright ([#9557](https://github.com/earendil-works/pi/issues/9557))
|
||||
|
||||
## [1.5.0] - 2026-11-02
|
||||
|
||||
|
|
|
|||
|
|
@ -1632,11 +1632,20 @@ function convertTools(
|
|||
? undefined
|
||||
: resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
|
||||
const parameters = getJsonSchemaToolParameters(tool, strict);
|
||||
const schema = parameters as { properties?: unknown; required?: string[] };
|
||||
const schema = parameters as {
|
||||
properties?: unknown;
|
||||
required?: string[];
|
||||
$defs?: unknown;
|
||||
definitions?: unknown;
|
||||
};
|
||||
// Keep shared definitions so property $refs still resolve. Other root keywords stay out:
|
||||
// Anthropic rejects root anyOf/oneOf/allOf with a 400.
|
||||
const legacyInputSchema = {
|
||||
type: "object" as const,
|
||||
properties: schema.properties ?? {},
|
||||
required: schema.required ?? [],
|
||||
...(schema.$defs === undefined ? {} : { $defs: schema.$defs }),
|
||||
...(schema.definitions === undefined ? {} : { definitions: schema.definitions }),
|
||||
};
|
||||
const inputSchema =
|
||||
strict === true
|
||||
|
|
|
|||
|
|
@ -98,6 +98,31 @@ describe("Anthropic strict tool schemas", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// https://github.com/earendil-works/pi/issues/9557
|
||||
it("keeps root definitions in non-strict schemas so property refs resolve", async () => {
|
||||
const settings = { type: "object", properties: { verbose: { type: "boolean" } } };
|
||||
const cases = [
|
||||
{ ref: "#/$defs/Settings", definitions: { $defs: { Settings: settings } } },
|
||||
{ ref: "#/definitions/Settings", definitions: { definitions: { Settings: settings } } },
|
||||
];
|
||||
for (const { ref, definitions } of cases) {
|
||||
const parameters = {
|
||||
type: "object",
|
||||
properties: { settings: { $ref: ref } },
|
||||
required: ["settings"],
|
||||
...definitions,
|
||||
} as unknown as Tool["parameters"];
|
||||
const tool = await captureFirstTool(createTool(parameters));
|
||||
expect(tool.strict).toBeUndefined();
|
||||
expect(tool.input_schema).toEqual({
|
||||
type: "object",
|
||||
properties: { settings: { $ref: ref } },
|
||||
required: ["settings"],
|
||||
...definitions,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// https://github.com/earendil-works/pi/issues/9953
|
||||
it("sends prefer tools non-strict when they use keywords Anthropic strict mode rejects", async () => {
|
||||
const unsupportedParameters: Tool["parameters"][] = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue