mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Zod-4: for JSON schema
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import type { AixAPIChatGenerate_Request } from '~/modules/aix/server/api/aix.wiretypes';
|
||||
import { aixCGR_ChatSequence_FromDMessagesOrThrow, aixCGR_SystemMessageText } from '~/modules/aix/client/aix.client.chatGenerateRequest';
|
||||
@@ -24,7 +24,7 @@ export async function agiAttachmentPrompts(attachmentFragments: DMessageAttachme
|
||||
|
||||
const num_suggestions = 3;
|
||||
|
||||
const inputSchema = z.object({
|
||||
const inputSchema = z.object({ // zod-4
|
||||
attachments_analysis: z.array(
|
||||
z.object({
|
||||
name: z.string().describe('Identifier of the file.'),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ZodObject } from 'zod';
|
||||
import type { ZodObject, ZodString } from 'zod/v4';
|
||||
|
||||
import type { AixAPIChatGenerate_Request } from '~/modules/aix/server/api/aix.wiretypes';
|
||||
import { aixChatGenerateContent_DMessage, aixCreateChatGenerateContext } from '~/modules/aix/client/aix.client';
|
||||
@@ -17,7 +17,9 @@ interface CodeFix {
|
||||
userInstructionTemplate: string; // Template with placeholders for `codeToFix` and `errorString`
|
||||
functionName: string;
|
||||
functionPolicy: 'invoke' | 'think-then-invoke';
|
||||
outputSchema: ZodObject<any>;
|
||||
outputSchema: ZodObject<{
|
||||
corrected_code: ZodString;
|
||||
}>;
|
||||
}
|
||||
|
||||
const CodeFixes: Record<string, CodeFix> = {};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import type { AixAPIChatGenerate_Request } from '~/modules/aix/server/api/aix.wiretypes';
|
||||
import { AixClientFunctionCallToolDefinition, aixFunctionCallTool, aixRequireSingleFunctionCallInvocation } from '~/modules/aix/client/aix.client.fromSimpleFunction';
|
||||
@@ -70,9 +70,9 @@ Analyze the following short exchange and call the function {{functionName}} with
|
||||
fun: {
|
||||
name: 'draw_plantuml_diagram',
|
||||
description: 'Generates a PlantUML diagram or mindmap from the last message, if applicable, very useful to the user, and no other diagrams are present.',
|
||||
inputSchema: z.object({
|
||||
inputSchema: z.object({ // zod-4
|
||||
rating_short_reason: z.string().describe('A 4-10 words reason on whether the diagram would be desired by the user or not.'),
|
||||
rating_number: z.number().int().describe('The relevance of the diagram to the conversation, on a scale of 1 to 5 . If lower than 4, STOP.'),
|
||||
rating_number: z.number().describe('The relevance of the diagram to the conversation, on a scale of 1 to 5 . If lower than 4, STOP.'),
|
||||
type: z.string().describe('The most suitable PlantUML diagram type: sequence, usecase, class, activity, component, state, object, deployment, timing, network, wireframe, gantt, wbs or mindmap.').optional(),
|
||||
code: z.string().describe('A valid PlantUML string (@startuml...@enduml) to be rendered as a diagram or mindmap (@startmindmap...@endmindmap), or empty. No external references allowed. Use one or more asterisks to indent and separate with spaces.').optional(),
|
||||
}),
|
||||
@@ -113,10 +113,10 @@ Please follow closely the following requirements:
|
||||
fun: {
|
||||
name: suggestUIFunctionName,
|
||||
description: 'Renders a web UI when provided with a single concise HTML5 string (can include CSS and JS), if applicable and relevant.',
|
||||
inputSchema: z.object({
|
||||
inputSchema: z.object({ // zod-4
|
||||
possible_ui_requirements: z.string().describe('Brief (10 words) to medium length (40 words) requirements for the UI. Include main features, looks, and layout.'),
|
||||
rating_short_reason: z.string().describe('A 4-10 word reason on whether the UI would be desired by the user or not.'),
|
||||
rating_number: z.number().int().describe('The relevance of the UI to the conversation, on a scale of 1 (does not add much value), 2 (superfluous), 3 (helps a lot in understanding), 4 (essential) to 5 (fundamental to the understanding). If 1 or 2, do not proceed and STOP.'),
|
||||
rating_number: z.number().describe('The relevance of the UI to the conversation, on a scale of 1 (does not add much value), 2 (superfluous), 3 (helps a lot in understanding), 4 (essential) to 5 (fundamental to the understanding). If 1 or 2, do not proceed and STOP.'),
|
||||
html: z.string().describe('A valid HTML string containing the user interface code. The code should be complete, with no dependencies, lower case, and include minimal inline CSS if needed. The UI should be visual and interactive.').optional(),
|
||||
file_name: z.string().describe('Short letters-and-dashes file name of the HTML without the .html extension.').optional(),
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ZodSchema } from 'zod';
|
||||
import { JsonSchema7ObjectType, zodToJsonSchema } from 'zod-to-json-schema';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import type { AixTools_FunctionCallDefinition } from '../server/api/aix.wiretypes';
|
||||
import { DMessageContentFragment, DMessageToolInvocationPart, DMessageVoidFragment, isContentFragment } from '~/common/stores/chat/chat.fragments';
|
||||
@@ -17,7 +16,7 @@ export type AixClientFunctionCallToolDefinition = {
|
||||
* We only accept objects, not arrays - as downstream APIs have spotty implementation for non-object.
|
||||
* If the function does not take any inputs, use `Zod.object({})` or Zod.void().
|
||||
*/
|
||||
inputSchema: ZodSchema<object /*| void*/>;
|
||||
inputSchema: z.ZodObject; // zod-4 object
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +25,19 @@ export type AixClientFunctionCallToolDefinition = {
|
||||
* @param functionCall
|
||||
*/
|
||||
export function aixFunctionCallTool(functionCall: AixClientFunctionCallToolDefinition): AixTools_FunctionCallDefinition {
|
||||
const { properties, required } = zodToJsonSchema(functionCall.inputSchema, { $refStrategy: 'none' }) as JsonSchema7ObjectType;
|
||||
|
||||
// convert a Zod schema to JSON Schema
|
||||
const { properties, required } = z.toJSONSchema(functionCall.inputSchema, {
|
||||
// config
|
||||
io: 'input', // avoids AdditionalProperties by looking at the Zod schema from the input perspective
|
||||
target: 'draft-2020-12', // (default) newest standard
|
||||
reused: 'inline', // (default) inline reused schemas
|
||||
|
||||
// [DEV] makes sure we specify good tool definitions
|
||||
cycles: 'throw',
|
||||
unrepresentable: 'throw',
|
||||
});
|
||||
|
||||
const takesNoInputs = !Object.keys(properties || {}).length;
|
||||
return {
|
||||
type: 'function_call',
|
||||
@@ -35,8 +46,10 @@ export function aixFunctionCallTool(functionCall: AixClientFunctionCallToolDefin
|
||||
description: functionCall.description,
|
||||
...(!takesNoInputs && {
|
||||
input_schema: {
|
||||
properties: _recursiveObjectSchemaCleanup(properties),
|
||||
...(required && { required }),
|
||||
properties: properties as any, // FIXME: remove the 'as any' after the full migration to zod-4
|
||||
...(!!required?.length && {
|
||||
required: required,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
@@ -44,31 +57,6 @@ export function aixFunctionCallTool(functionCall: AixClientFunctionCallToolDefin
|
||||
}
|
||||
|
||||
|
||||
/* Recursive function to clean up the Schema object, to:
|
||||
* - remove extra 'additionalProperties' keys
|
||||
* - reorder the keys of object/array description objects to be: ['type', 'description', ..., 'required']
|
||||
*/
|
||||
function _recursiveObjectSchemaCleanup(obj: Record<string, any>, thisKey?: string): Record<string, any> {
|
||||
if (typeof obj !== 'object' || obj === null)
|
||||
return obj; // leaf node
|
||||
|
||||
const { additionalProperties: _, ...rest } = obj;
|
||||
|
||||
// 'properties' are ordered and we don't want to re-sort them
|
||||
if (thisKey === 'properties') {
|
||||
return Object.fromEntries(Object.entries(rest).map(([key, value]) => [key, _recursiveObjectSchemaCleanup(value, key)]));
|
||||
}
|
||||
|
||||
const { type, description, required, ...others } = rest;
|
||||
return {
|
||||
...(type && { type }),
|
||||
...(description && { description }),
|
||||
...Object.fromEntries(Object.entries(others).map(([key, value]) => [key, _recursiveObjectSchemaCleanup(value, key)])),
|
||||
...(required && { required }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/** Extract the function name from the Aix FunctionCall Tool Definition */
|
||||
export function aixRequireSingleFunctionCallInvocation(fragments: (DMessageContentFragment | DMessageVoidFragment)[], expectedFunctionName: string, allowThinkPart: boolean, debugLabel: string): {
|
||||
invocation: Extract<DMessageToolInvocationPart['invocation'], { type: 'function_call' }>;
|
||||
|
||||
Reference in New Issue
Block a user