kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: fix JSON schema parsing in @agentic/core
rodzic
938be099df
commit
66f8c1e900
|
@ -1,5 +1,5 @@
|
|||
import type { AdminDeployment, Tool } from '@agentic/platform-types'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
import { assert, HttpError } from '@agentic/platform-core'
|
||||
|
||||
import type { GatewayHonoContext } from './types'
|
||||
import { cfValidateJsonSchema } from './cf-validate-json-schema'
|
||||
|
@ -14,6 +14,7 @@ export async function getToolArgsFromRequest(
|
|||
deployment: AdminDeployment
|
||||
}
|
||||
): Promise<Record<string, any>> {
|
||||
const logger = ctx.get('logger')
|
||||
const request = ctx.req.raw
|
||||
assert(
|
||||
deployment.origin.type !== 'raw',
|
||||
|
@ -50,17 +51,29 @@ export async function getToolArgsFromRequest(
|
|||
|
||||
try {
|
||||
incomingRequestArgsRaw = (await request.json()) as Record<string, any>
|
||||
} catch {
|
||||
// If the request body is not JSON or malformed, ignore it for now.
|
||||
// TODO: need to improve on this logic.
|
||||
} catch (err) {
|
||||
// Error if the request body is not JSON or is malformed.
|
||||
logger.error('Error parsing incoming request body', request, err)
|
||||
throw new HttpError({
|
||||
message: 'Invalid request body json',
|
||||
statusCode: 400,
|
||||
cause: err
|
||||
})
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// 'incomingRequestArgsRaw',
|
||||
// typeof incomingRequestArgsRaw,
|
||||
// request.headers,
|
||||
// incomingRequestArgsRaw
|
||||
// )
|
||||
|
||||
// TODO: Proper support for empty params with POST requests
|
||||
assert(incomingRequestArgsRaw, 400, 'Invalid empty request body')
|
||||
assert(
|
||||
typeof incomingRequestArgsRaw === 'object',
|
||||
400,
|
||||
'Invalid request body'
|
||||
`Invalid request body: expected type "object", received type "${typeof incomingRequestArgsRaw}"`
|
||||
)
|
||||
assert(!Array.isArray(incomingRequestArgsRaw), 400, 'Invalid request body')
|
||||
return incomingRequestArgsRaw
|
||||
|
|
|
@ -41,8 +41,6 @@ async function main() {
|
|||
})
|
||||
}
|
||||
|
||||
console.log()
|
||||
|
||||
{
|
||||
// Second call to OpenAI to generate a text response
|
||||
const res = await openai.responses.create({
|
||||
|
|
|
@ -40,8 +40,6 @@ async function main() {
|
|||
})
|
||||
}
|
||||
|
||||
console.log()
|
||||
|
||||
{
|
||||
// Second call to OpenAI to generate a text response
|
||||
const res = await openai.chat.completions.create({
|
||||
|
@ -51,7 +49,7 @@ async function main() {
|
|||
tools: searchTool.functions.toolSpecs
|
||||
})
|
||||
const message = res.choices?.[0]?.message
|
||||
console.log(message)
|
||||
console.log(message?.content)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -183,20 +183,26 @@ function getSchema(parameters: any[], type: string) {
|
|||
...paramSchema,
|
||||
...handleNullableSchema(param.schema)
|
||||
}
|
||||
|
||||
if ('examples' in param) {
|
||||
paramSchema.examples = getExamples(param.examples)
|
||||
}
|
||||
|
||||
schema.properties[param.name] = paramSchema
|
||||
} else {
|
||||
if ('examples' in paramSchema) {
|
||||
paramSchema.examples = getExamples(paramSchema.examples)
|
||||
}
|
||||
|
||||
schema.properties[param.name] = param.nullable
|
||||
? handleNullable(paramSchema)
|
||||
: paramSchema
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support openai strict mode by default (all params must be required,
|
||||
// and optional params without defaults must be nullable)
|
||||
|
||||
schema.required = getRequiredParams(params)
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,9 @@ export class AgenticToolClient extends AIFunctionsProvider {
|
|||
name: tool.name,
|
||||
description: tool.description ?? '',
|
||||
inputSchema: createJsonSchema(tool.inputSchema),
|
||||
// TODO: we should make sure all agentic tools support OpenAI strict
|
||||
// mode by default.
|
||||
strict: false,
|
||||
execute: async (json) => {
|
||||
return ky
|
||||
.post(
|
||||
|
|
|
@ -476,7 +476,7 @@ catalogs:
|
|||
version: 3.25.67
|
||||
zod-to-json-schema:
|
||||
specifier: ^3.24.5
|
||||
version: 3.24.5
|
||||
version: 3.24.6
|
||||
zod-validation-error:
|
||||
specifier: ^3.5.2
|
||||
version: 3.5.2
|
||||
|
@ -1429,7 +1429,7 @@ importers:
|
|||
version: 5.1.0
|
||||
zod-to-json-schema:
|
||||
specifier: 'catalog:'
|
||||
version: 3.24.5(zod@3.25.67)
|
||||
version: 3.24.6(zod@3.25.67)
|
||||
|
||||
packages/validators:
|
||||
dependencies:
|
||||
|
@ -1473,6 +1473,9 @@ importers:
|
|||
ky:
|
||||
specifier: 'catalog:'
|
||||
version: 1.8.1
|
||||
openai-zod-to-json-schema:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1(zod@3.25.67)
|
||||
p-throttle:
|
||||
specifier: 'catalog:'
|
||||
version: 6.2.0
|
||||
|
@ -1482,9 +1485,6 @@ importers:
|
|||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 3.25.67
|
||||
zod-to-json-schema:
|
||||
specifier: 'catalog:'
|
||||
version: 3.24.5(zod@3.25.67)
|
||||
zod-validation-error:
|
||||
specifier: 'catalog:'
|
||||
version: 3.5.2(zod@3.25.67)
|
||||
|
@ -6338,11 +6338,6 @@ packages:
|
|||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
browserslist@4.25.0:
|
||||
resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
browserslist@4.25.1:
|
||||
resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
|
@ -6436,9 +6431,6 @@ packages:
|
|||
peerDependencies:
|
||||
three: '>=0.126.1'
|
||||
|
||||
caniuse-lite@1.0.30001723:
|
||||
resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
|
||||
|
||||
caniuse-lite@1.0.30001726:
|
||||
resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==}
|
||||
|
||||
|
@ -7109,9 +7101,6 @@ packages:
|
|||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
electron-to-chromium@1.5.167:
|
||||
resolution: {integrity: sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==}
|
||||
|
||||
electron-to-chromium@1.5.177:
|
||||
resolution: {integrity: sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==}
|
||||
|
||||
|
@ -9514,8 +9503,8 @@ packages:
|
|||
resolution: {integrity: sha512-8EcOGJk/JXFaoGjeFM53Z3zBnwOpKtZeu5X0wts67WqA1PTnsmwRgUw9aGAsQ5V6cuTfJUv282h1ypFgDGPDSA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
openai-zod-to-json-schema@1.0.3:
|
||||
resolution: {integrity: sha512-CFU+KtOmX1dk2nPCZcGYgbrI3YLJJgMSehx1mLbH1A2fsRmZevHzMau6vFIhtkCpHWkGQ3ossA4a0OzVHlGrkw==}
|
||||
openai-zod-to-json-schema@1.1.1:
|
||||
resolution: {integrity: sha512-WIsQn2aXqqhRKVoDAQ7UG2W7K6q4FuJL7sn6lrj4bIHC6bbTYNFDfIw10yWCW3GX/zP2Psvubcmcb2NOCnSzsA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.23.8
|
||||
|
@ -11644,11 +11633,6 @@ packages:
|
|||
zod-from-json-schema@0.0.5:
|
||||
resolution: {integrity: sha512-zYEoo86M1qpA1Pq6329oSyHLS785z/mTwfr9V1Xf/ZLhuuBGaMlDGu/pDVGVUe4H4oa1EFgWZT53DP0U3oT9CQ==}
|
||||
|
||||
zod-to-json-schema@3.24.5:
|
||||
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
|
||||
zod-to-json-schema@3.24.6:
|
||||
resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==}
|
||||
peerDependencies:
|
||||
|
@ -11713,7 +11697,7 @@ snapshots:
|
|||
delay: 6.0.0
|
||||
jsonrepair: 3.12.0
|
||||
ky: 1.8.1
|
||||
openai-zod-to-json-schema: 1.0.3(zod@3.25.67)
|
||||
openai-zod-to-json-schema: 1.1.1(zod@3.25.67)
|
||||
p-throttle: 6.2.0
|
||||
type-fest: 4.41.0
|
||||
zod: 3.25.67
|
||||
|
@ -16690,14 +16674,13 @@ snapshots:
|
|||
|
||||
'@types/pg-pool@2.0.6':
|
||||
dependencies:
|
||||
'@types/pg': 8.6.1
|
||||
'@types/pg': 8.15.4
|
||||
|
||||
'@types/pg@8.15.4':
|
||||
dependencies:
|
||||
'@types/node': 24.0.7
|
||||
pg-protocol: 1.10.3
|
||||
pg-types: 2.2.0
|
||||
optional: true
|
||||
|
||||
'@types/pg@8.6.1':
|
||||
dependencies:
|
||||
|
@ -17165,8 +17148,8 @@ snapshots:
|
|||
|
||||
autoprefixer@10.4.21(postcss@8.5.6):
|
||||
dependencies:
|
||||
browserslist: 4.25.0
|
||||
caniuse-lite: 1.0.30001723
|
||||
browserslist: 4.25.1
|
||||
caniuse-lite: 1.0.30001726
|
||||
fraction.js: 4.3.7
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.1.1
|
||||
|
@ -17300,13 +17283,6 @@ snapshots:
|
|||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
browserslist@4.25.0:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001723
|
||||
electron-to-chromium: 1.5.167
|
||||
node-releases: 2.0.19
|
||||
update-browserslist-db: 1.1.3(browserslist@4.25.0)
|
||||
|
||||
browserslist@4.25.1:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001726
|
||||
|
@ -17417,8 +17393,6 @@ snapshots:
|
|||
dependencies:
|
||||
three: 0.177.0
|
||||
|
||||
caniuse-lite@1.0.30001723: {}
|
||||
|
||||
caniuse-lite@1.0.30001726: {}
|
||||
|
||||
cannon-es-debugger@1.0.0(cannon-es@0.20.0)(three@0.177.0)(typescript@5.8.3):
|
||||
|
@ -17954,8 +17928,6 @@ snapshots:
|
|||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
electron-to-chromium@1.5.167: {}
|
||||
|
||||
electron-to-chromium@1.5.177: {}
|
||||
|
||||
email-validator@2.0.4: {}
|
||||
|
@ -20778,7 +20750,7 @@ snapshots:
|
|||
'@swc/counter': 0.1.3
|
||||
'@swc/helpers': 0.5.15
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001723
|
||||
caniuse-lite: 1.0.30001726
|
||||
postcss: 8.4.31
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
@ -20983,7 +20955,7 @@ snapshots:
|
|||
dependencies:
|
||||
ky: 1.8.1
|
||||
|
||||
openai-zod-to-json-schema@1.0.3(zod@3.25.67):
|
||||
openai-zod-to-json-schema@1.1.1(zod@3.25.67):
|
||||
dependencies:
|
||||
zod: 3.25.67
|
||||
|
||||
|
@ -23074,12 +23046,6 @@ snapshots:
|
|||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
update-browserslist-db@1.1.3(browserslist@4.25.0):
|
||||
dependencies:
|
||||
browserslist: 4.25.0
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
update-browserslist-db@1.1.3(browserslist@4.25.1):
|
||||
dependencies:
|
||||
browserslist: 4.25.1
|
||||
|
@ -23473,10 +23439,6 @@ snapshots:
|
|||
dependencies:
|
||||
zod: 3.25.67
|
||||
|
||||
zod-to-json-schema@3.24.5(zod@3.25.67):
|
||||
dependencies:
|
||||
zod: 3.25.67
|
||||
|
||||
zod-to-json-schema@3.24.6(zod@3.25.67):
|
||||
dependencies:
|
||||
zod: 3.25.67
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
../.. | WARN `node_modules` is present. Lockfile only installation will make it out-of-date
|
||||
../.. | Progress: resolved 1, reused 0, downloaded 0, added 0
|
|
@ -35,7 +35,7 @@
|
|||
"delay": "catalog:",
|
||||
"jsonrepair": "catalog:",
|
||||
"ky": "catalog:",
|
||||
"zod-to-json-schema": "catalog:",
|
||||
"openai-zod-to-json-schema": "^1.1.1",
|
||||
"p-throttle": "catalog:",
|
||||
"type-fest": "catalog:",
|
||||
"zod-validation-error": "catalog:"
|
||||
|
|
|
@ -114,7 +114,7 @@ export function createAIFunction<
|
|||
const args = input.function_call?.arguments
|
||||
assert(
|
||||
args,
|
||||
`Missing required function_call.arguments for function ${name}`
|
||||
`Missing required function_call.arguments for function "${name}"`
|
||||
)
|
||||
return inputAgenticSchema.parse(args)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { z } from 'zod'
|
||||
import { jsonrepair } from 'jsonrepair'
|
||||
|
||||
import type * as types from './types'
|
||||
import { parseStructuredOutput } from './parse-structured-output'
|
||||
|
@ -102,7 +103,7 @@ export function asZodOrJsonSchema<TData>(
|
|||
export function createJsonSchema<TData = unknown>(
|
||||
jsonSchema: types.JSONSchema,
|
||||
{
|
||||
parse = (value) => value as TData,
|
||||
parse,
|
||||
safeParse,
|
||||
source
|
||||
}: {
|
||||
|
@ -111,6 +112,15 @@ export function createJsonSchema<TData = unknown>(
|
|||
source?: any
|
||||
} = {}
|
||||
): AgenticSchema<TData> {
|
||||
parse ??= (value: unknown) => {
|
||||
if (typeof value === 'string') {
|
||||
value = JSON.parse(jsonrepair(value))
|
||||
}
|
||||
|
||||
// TODO: use `cfValidateJsonSchema` from `@agentic/json-schema` here.
|
||||
return value as TData
|
||||
}
|
||||
|
||||
safeParse ??= (value: unknown) => {
|
||||
try {
|
||||
const result = parse(value)
|
||||
|
|
|
@ -60,4 +60,38 @@ describe('zodToJsonSchema', () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('handles optional properties in strict mode', () => {
|
||||
const params = zodToJsonSchema(
|
||||
z.object({
|
||||
name: z.string().optional(),
|
||||
age: z.number().optional().default(10)
|
||||
}),
|
||||
{
|
||||
strict: true
|
||||
}
|
||||
)
|
||||
|
||||
expect(params).toEqual({
|
||||
additionalProperties: false,
|
||||
type: 'object',
|
||||
required: ['name', 'age'],
|
||||
properties: {
|
||||
name: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
]
|
||||
},
|
||||
age: {
|
||||
type: 'number',
|
||||
default: 10
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { z } from 'zod'
|
||||
import { zodToJsonSchema as zodToJsonSchemaImpl } from 'zod-to-json-schema'
|
||||
import { zodToJsonSchema as zodToJsonSchemaImpl } from 'openai-zod-to-json-schema'
|
||||
|
||||
import type * as types from './types'
|
||||
import { omit } from './utils'
|
||||
|
@ -16,7 +16,7 @@ export function zodToJsonSchema(
|
|||
return omit(
|
||||
zodToJsonSchemaImpl(schema, {
|
||||
$refStrategy: 'none',
|
||||
target: strict ? 'openAi' : undefined
|
||||
openaiStrictMode: strict
|
||||
}),
|
||||
'$schema',
|
||||
'default',
|
||||
|
|
Ładowanie…
Reference in New Issue