77import {
88 BaseLanguageModelInput ,
99 StructuredOutputMethodOptions ,
10+ FunctionDefinition ,
1011} from "@langchain/core/language_models/base" ;
1112import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager" ;
1213import {
@@ -28,7 +29,6 @@ import type {
2829} from "ollama" ;
2930import {
3031 Runnable ,
31- RunnableLambda ,
3232 RunnablePassthrough ,
3333 RunnableSequence ,
3434} from "@langchain/core/runnables" ;
@@ -41,14 +41,14 @@ import {
4141import {
4242 InteropZodType ,
4343 isInteropZodSchema ,
44- interopParseAsync ,
4544} from "@langchain/core/utils/types" ;
4645import { toJsonSchema } from "@langchain/core/utils/json_schema" ;
4746import {
4847 convertOllamaMessagesToLangChain ,
4948 convertToOllamaMessages ,
5049} from "./utils.js" ;
5150import { OllamaCamelCaseOptions } from "./types.js" ;
51+ import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools" ;
5252
5353export interface ChatOllamaCallOptions extends BaseChatModelCallOptions {
5454 /**
@@ -834,121 +834,127 @@ export class ChatOllama
834834 parsed : RunOutput ;
835835 }
836836 > {
837- if ( config ?. method === undefined || config ?. method === "jsonSchema" ) {
838- const outputSchemaIsZod = isInteropZodSchema ( outputSchema ) ;
839- const jsonSchema = outputSchemaIsZod
840- ? toJsonSchema ( outputSchema )
841- : outputSchema ;
842- const functionName = config ?. name ?? "extract" ;
843- const llm = this . bindTools ( [
844- {
845- type : "function" as const ,
846- function : {
837+ let llm : Runnable < BaseLanguageModelInput > ;
838+ let outputParser : Runnable < AIMessageChunk , RunOutput > ;
839+
840+ const { schema, name, includeRaw } = {
841+ ...config ,
842+ schema : outputSchema ,
843+ } ;
844+ const method = config ?. method ?? "jsonSchema" ;
845+
846+ if ( method === "functionCalling" ) {
847+ let functionName = name ?? "extract" ;
848+ if ( isInteropZodSchema ( schema ) ) {
849+ const jsonSchema = toJsonSchema ( schema ) ;
850+ llm = this . bindTools ( [
851+ {
852+ type : "function" ,
853+ function : {
854+ name : functionName ,
855+ description : jsonSchema . description ,
856+ parameters : jsonSchema ,
857+ } ,
858+ } ,
859+ ] ) . withConfig ( {
860+ ls_structured_output_format : {
861+ kwargs : { method } ,
862+ schema : jsonSchema ,
863+ } ,
864+ } as Partial < ChatOllamaCallOptions > ) ;
865+ outputParser = new JsonOutputKeyToolsParser ( {
866+ returnSingle : true ,
867+ keyName : functionName ,
868+ zodSchema : schema ,
869+ } ) ;
870+ } else {
871+ let openAIFunctionDefinition : FunctionDefinition ;
872+ if (
873+ typeof schema . name === "string" &&
874+ typeof schema . parameters === "object" &&
875+ schema . parameters != null
876+ ) {
877+ openAIFunctionDefinition = schema as FunctionDefinition ;
878+ functionName = schema . name ;
879+ } else {
880+ openAIFunctionDefinition = {
847881 name : functionName ,
848- description : jsonSchema . description ,
849- parameters : jsonSchema ,
882+ description : schema . description ?? "" ,
883+ parameters : schema ,
884+ } ;
885+ }
886+ llm = this . bindTools ( [
887+ {
888+ type : "function" ,
889+ function : openAIFunctionDefinition ,
850890 } ,
851- } ,
852- ] ) . withConfig ( {
891+ ] ) . withConfig ( {
892+ ls_structured_output_format : {
893+ kwargs : { method } ,
894+ schema,
895+ } ,
896+ } as Partial < ChatOllamaCallOptions > ) ;
897+ outputParser = new JsonOutputKeyToolsParser < RunOutput > ( {
898+ returnSingle : true ,
899+ keyName : functionName ,
900+ } ) ;
901+ }
902+ } else if ( method === "jsonMode" ) {
903+ outputParser = isInteropZodSchema ( schema )
904+ ? StructuredOutputParser . fromZodSchema ( schema )
905+ : new JsonOutputParser < RunOutput > ( ) ;
906+ const jsonSchema = toJsonSchema ( schema ) ;
907+ llm = this . withConfig ( {
853908 format : "json" ,
854909 ls_structured_output_format : {
855- kwargs : { method : "jsonSchema" } ,
856- schema : toJsonSchema ( outputSchema ) ,
910+ kwargs : { method } ,
911+ schema : jsonSchema ,
857912 } ,
858- } ) ;
859-
860- /**
861- * Create a parser that handles both tool calls and JSON content
862- */
863- const outputParser = RunnableLambda . from < BaseMessage , RunOutput > (
864- async ( input : BaseMessage ) : Promise < RunOutput > => {
865- /**
866- * Ensure input is an AI message (either AIMessage or AIMessageChunk)
867- */
868- if (
869- ! AIMessage . isInstance ( input ) &&
870- ! AIMessageChunk . isInstance ( input )
871- ) {
872- throw new Error ( "Input is not an AIMessage or AIMessageChunk." ) ;
873- }
874-
875- /**
876- * First, check if there are tool calls - extract args from the tool call
877- */
878- if ( input . tool_calls && input . tool_calls . length > 0 ) {
879- const toolCall = input . tool_calls . find (
880- ( tc ) => tc . name === functionName
881- ) ;
882- if ( toolCall && toolCall . args ) {
883- /**
884- * Validate with schema if Zod schema is provided
885- */
886- if ( outputSchemaIsZod ) {
887- return await interopParseAsync (
888- outputSchema as InteropZodType < RunOutput > ,
889- toolCall . args
890- ) ;
891- }
892- return toolCall . args as RunOutput ;
893- }
894- }
895-
896- /**
897- * Fallback: parse content as JSON (when format: "json" is set)
898- */
899- const content =
900- typeof input . content === "string" ? input . content : "" ;
901- if ( ! content ) {
902- throw new Error (
903- "No tool calls found and content is empty. Cannot parse structured output."
904- ) ;
905- }
906-
907- /**
908- * Use the appropriate parser based on schema type
909- */
910- if ( outputSchemaIsZod ) {
911- const zodParser = StructuredOutputParser . fromZodSchema (
912- outputSchema as InteropZodType < RunOutput >
913- ) ;
914- return await zodParser . parse ( content ) ;
915- } else {
916- const jsonParser = new JsonOutputParser < RunOutput > ( ) ;
917- return await jsonParser . parse ( content ) ;
918- }
919- }
913+ } as Partial < ChatOllamaCallOptions > ) ;
914+ } else if ( method === "jsonSchema" ) {
915+ outputParser = isInteropZodSchema ( schema )
916+ ? StructuredOutputParser . fromZodSchema ( schema )
917+ : new JsonOutputParser < RunOutput > ( ) ;
918+ const jsonSchema = toJsonSchema ( schema ) ;
919+ llm = this . withConfig ( {
920+ format : jsonSchema ,
921+ ls_structured_output_format : {
922+ kwargs : { method } ,
923+ schema : jsonSchema ,
924+ } ,
925+ } as Partial < ChatOllamaCallOptions > ) ;
926+ } else {
927+ throw new TypeError (
928+ `Unrecognized structured output method '${ method } '. Expected one of 'functionCalling', 'jsonSchema', or 'jsonMode'`
920929 ) ;
930+ }
921931
922- if ( ! config ?. includeRaw ) {
923- return llm . pipe ( outputParser ) as Runnable <
924- BaseLanguageModelInput ,
925- RunOutput
926- > ;
927- }
932+ if ( ! includeRaw ) {
933+ return llm . pipe ( outputParser ) . withConfig ( {
934+ runName : "ChatOllamaStructuredOutput" ,
935+ } ) as Runnable < BaseLanguageModelInput , RunOutput > ;
936+ }
928937
929- const parserAssign = RunnablePassthrough . assign ( {
930- // eslint-disable-next-line @typescript-eslint/no-explicit-any
931- parsed : ( input : any , config ) => outputParser . invoke ( input . raw , config ) ,
932- } ) ;
933- const parserNone = RunnablePassthrough . assign ( {
934- parsed : ( ) => null ,
935- } ) ;
936- const parsedWithFallback = parserAssign . withFallbacks ( {
937- fallbacks : [ parserNone ] ,
938- } ) ;
939- return RunnableSequence . from <
940- BaseLanguageModelInput ,
941- { raw : BaseMessage ; parsed : RunOutput }
942- > ( [
943- {
944- raw : llm ,
945- } ,
946- parsedWithFallback ,
947- ] ) ;
948- } else {
949- // TODO: Fix this type in core
938+ const parserAssign = RunnablePassthrough . assign ( {
950939 // eslint-disable-next-line @typescript-eslint/no-explicit-any
951- return super . withStructuredOutput < RunOutput > ( outputSchema , config as any ) ;
952- }
940+ parsed : ( input : any , config ) => outputParser . invoke ( input . raw , config ) ,
941+ } ) ;
942+ const parserNone = RunnablePassthrough . assign ( {
943+ parsed : ( ) => null ,
944+ } ) ;
945+ const parsedWithFallback = parserAssign . withFallbacks ( {
946+ fallbacks : [ parserNone ] ,
947+ } ) ;
948+ return RunnableSequence . from <
949+ BaseLanguageModelInput ,
950+ { raw : BaseMessage ; parsed : RunOutput }
951+ > ( [
952+ {
953+ raw : llm ,
954+ } ,
955+ parsedWithFallback ,
956+ ] ) . withConfig ( {
957+ runName : "StructuredOutputRunnable" ,
958+ } ) ;
953959 }
954960}
0 commit comments