@@ -10,12 +10,14 @@ import { type AIMessage } from "@langchain/core/messages";
1010import { type LanguageModelLike } from "@langchain/core/language_models/base" ;
1111import { toJsonSchema , Validator } from "@langchain/core/utils/json_schema" ;
1212import { type FunctionDefinition } from "@langchain/core/language_models/base" ;
13+ import { type BaseChatModel } from "@langchain/core/language_models/chat_models" ;
1314
15+ import { initChatModel } from "../chat_models/universal.js" ;
1416import {
1517 StructuredOutputParsingError ,
1618 MultipleStructuredOutputsError ,
1719} from "./errors.js" ;
18- import { isConfigurableModel , isBaseChatModel } from "./model.js" ;
20+ import { isConfigurableModel } from "./model.js" ;
1921
2022/**
2123 * Special type to indicate that no response format is provided.
@@ -206,7 +208,7 @@ export type ResponseFormat = ToolStrategy<any> | ProviderStrategy<any>;
206208 * @param model - The model to check if it supports JSON schema output
207209 * @returns
208210 */
209- export function transformResponseFormat (
211+ export async function transformResponseFormat (
210212 responseFormat ?:
211213 | InteropZodType < any >
212214 | InteropZodType < any > [ ]
@@ -215,9 +217,8 @@ export function transformResponseFormat(
215217 | ResponseFormat
216218 | ToolStrategy < any > [ ]
217219 | ResponseFormatUndefined ,
218- options ?: ToolStrategyOptions ,
219220 model ?: LanguageModelLike | string
220- ) : ResponseFormat [ ] {
221+ ) : Promise < ResponseFormat [ ] > {
221222 if ( ! responseFormat ) {
222223 return [ ] ;
223224 }
@@ -237,62 +238,49 @@ export function transformResponseFormat(
237238 */
238239 if ( Array . isArray ( responseFormat ) ) {
239240 /**
240- * if every entry is a ToolStrategy or ProviderStrategy instance, return the array as is
241+ * we don't allow to have a list of ProviderStrategy instances
241242 */
242- if (
243- responseFormat . every (
244- ( item ) =>
245- item instanceof ToolStrategy || item instanceof ProviderStrategy
246- )
247- ) {
248- return responseFormat as unknown as ResponseFormat [ ] ;
249- }
250-
251- /**
252- * Check if all items are Zod schemas
253- */
254- if ( responseFormat . every ( ( item ) => isInteropZodObject ( item ) ) ) {
255- return responseFormat . map ( ( item ) =>
256- ToolStrategy . fromSchema ( item as InteropZodObject , options )
243+ if ( responseFormat . some ( ( item ) => item instanceof ProviderStrategy ) ) {
244+ throw new Error (
245+ "Invalid response format: list contains ProviderStrategy instances. You can only use a single ProviderStrategy instance instead."
257246 ) ;
258247 }
259248
260249 /**
261- * Check if all items are plain objects (JSON schema)
250+ * if every entry is a ToolStrategy or ProviderStrategy instance, return the array as is
262251 */
263- if (
264- responseFormat . every (
265- ( item ) =>
266- typeof item === "object" && item !== null && ! isInteropZodObject ( item )
267- )
268- ) {
269- return responseFormat . map ( ( item ) =>
270- ToolStrategy . fromSchema ( item as JsonSchemaFormat , options )
271- ) ;
252+ if ( responseFormat . every ( ( item ) => item instanceof ToolStrategy ) ) {
253+ return responseFormat as ResponseFormat [ ] ;
272254 }
273255
274256 throw new Error (
275- `Invalid response format: list contains mixed types .\n` +
257+ `Invalid response format: list contains invalid values .\n` +
276258 `All items must be either InteropZodObject or plain JSON schema objects.`
277259 ) ;
278260 }
279261
262+ /**
263+ * if the response format is a ToolStrategy or ProviderStrategy instance, return it as is
264+ */
280265 if (
281266 responseFormat instanceof ToolStrategy ||
282267 responseFormat instanceof ProviderStrategy
283268 ) {
284269 return [ responseFormat ] ;
285270 }
286271
287- const useProviderStrategy = hasSupportForJsonSchemaOutput ( model ) ;
272+ /**
273+ * If nothing is specified we have to check whether the model supports JSON schema output
274+ */
275+ const useProviderStrategy = await hasSupportForJsonSchemaOutput ( model ) ;
288276
289277 /**
290278 * `responseFormat` is a Zod schema
291279 */
292280 if ( isInteropZodObject ( responseFormat ) ) {
293281 return useProviderStrategy
294282 ? [ ProviderStrategy . fromSchema ( responseFormat ) ]
295- : [ ToolStrategy . fromSchema ( responseFormat , options ) ] ;
283+ : [ ToolStrategy . fromSchema ( responseFormat ) ] ;
296284 }
297285
298286 /**
@@ -305,7 +293,7 @@ export function transformResponseFormat(
305293 ) {
306294 return useProviderStrategy
307295 ? [ ProviderStrategy . fromSchema ( responseFormat as JsonSchemaFormat ) ]
308- : [ ToolStrategy . fromSchema ( responseFormat as JsonSchemaFormat , options ) ] ;
296+ : [ ToolStrategy . fromSchema ( responseFormat as JsonSchemaFormat ) ] ;
309297 }
310298
311299 throw new Error ( `Invalid response format: ${ String ( responseFormat ) } ` ) ;
@@ -380,7 +368,12 @@ export function toolStrategy(
380368 | JsonSchemaFormat [ ] ,
381369 options ?: ToolStrategyOptions
382370) : TypedToolStrategy {
383- return transformResponseFormat ( responseFormat , options ) as TypedToolStrategy ;
371+ const responseFormatArray = Array . isArray ( responseFormat )
372+ ? responseFormat
373+ : [ responseFormat ] ;
374+ return responseFormatArray . map ( ( item ) =>
375+ ToolStrategy . fromSchema ( item as InteropZodObject , options )
376+ ) as TypedToolStrategy ;
384377}
385378
386379export function providerStrategy < T extends InteropZodType < any > > (
@@ -419,76 +412,27 @@ export type JsonSchemaFormat = {
419412 __brand ?: never ;
420413} ;
421414
422- const CHAT_MODELS_THAT_SUPPORT_JSON_SCHEMA_OUTPUT = [ "ChatOpenAI" , "ChatXAI" ] ;
423- const MODEL_NAMES_THAT_SUPPORT_JSON_SCHEMA_OUTPUT = [
424- "grok" ,
425- "gpt-5" ,
426- "gpt-4.1" ,
427- "gpt-4o" ,
428- "gpt-oss" ,
429- "o3-pro" ,
430- "o3-mini" ,
431- ] ;
432-
433415/**
434416 * Identifies the models that support JSON schema output
435417 * @param model - The model to check
436418 * @returns True if the model supports JSON schema output, false otherwise
437419 */
438- export function hasSupportForJsonSchemaOutput (
420+ export async function hasSupportForJsonSchemaOutput (
439421 model ?: LanguageModelLike | string
440- ) : boolean {
422+ ) : Promise < boolean > {
441423 if ( ! model ) {
442424 return false ;
443425 }
444426
445- if ( typeof model === "string" ) {
446- const modelName = model . split ( ":" ) . pop ( ) as string ;
447- return MODEL_NAMES_THAT_SUPPORT_JSON_SCHEMA_OUTPUT . some (
448- ( modelNameSnippet ) => modelName . includes ( modelNameSnippet )
449- ) ;
450- }
451-
452- if ( isConfigurableModel ( model ) ) {
453- const configurableModel = model as unknown as {
454- _defaultConfig : { model : string } ;
455- } ;
456- return hasSupportForJsonSchemaOutput (
457- configurableModel . _defaultConfig . model
458- ) ;
459- }
460-
461- if ( ! isBaseChatModel ( model ) ) {
462- return false ;
463- }
427+ const resolvedModel =
428+ typeof model === "string"
429+ ? await initChatModel ( model )
430+ : ( model as BaseChatModel ) ;
464431
465- const chatModelClass = model . getName ( ) ;
466-
467- /**
468- * for testing purposes only
469- */
470- if ( chatModelClass === "FakeToolCallingChatModel" ) {
471- return true ;
472- }
473-
474- if (
475- CHAT_MODELS_THAT_SUPPORT_JSON_SCHEMA_OUTPUT . includes ( chatModelClass ) &&
476- /**
477- * OpenAI models
478- */ ( ( "model" in model &&
479- MODEL_NAMES_THAT_SUPPORT_JSON_SCHEMA_OUTPUT . some (
480- ( modelNameSnippet ) =>
481- typeof model . model === "string" &&
482- model . model . includes ( modelNameSnippet )
483- ) ) ||
484- /**
485- * for testing purposes only
486- */
487- ( chatModelClass === "FakeToolCallingModel" &&
488- "structuredResponse" in model ) )
489- ) {
490- return true ;
432+ if ( isConfigurableModel ( resolvedModel ) ) {
433+ const profile = await resolvedModel . _getProfile ( ) ;
434+ return profile . structuredOutput ?? false ;
491435 }
492436
493- return false ;
437+ return resolvedModel . profile . structuredOutput ?? false ;
494438}
0 commit comments