Skip to content

Conversation

hntrl
Copy link
Contributor

@hntrl hntrl commented Aug 20, 2025

some highlights from this iteration of the BaseChatModel class:

  • BaseChatModel now inherits from Runnable instead of BaseLanguageModel
    • this is to simplify the inheritance chain and use Message objects as the output of the "chat model runnable" instead of the legacy Generation object
  • adds a new callback manager that more closely models the new chat model inheritance structure
    • (e.g. chat models call handleChatModelX instead of handleLLMX)
    • also uses Message instead of Generation objects in events
  • adds an alternative implementation of BaseCache that uses Message objects instead of Generation objects
    • we technically are storing an array of Message's to later enable replaying stream chunks
  • instead of returning a RunnableBinding when calling withStructuredOutput, bindTools, or withConfig, we instead create a new instance of the current class by calling this.constructor and modifying an internal defaultOptions property that gets reused
    • for withStructuredOutput specifically, we're retaining runnable semantics by pinning the existing runnable chain to a .outputParser property that gets used in invoke and _streamIterator
    • fixes a long standing issue where you couldn't call withStructuredOutput and bindTools together
  • changes some of the abstract methods to be a little bit clearer for downstream implementations
    • _generate -> generate
    • generate -> invoke
    • _streamResponseChunks -> streamResponseChunks
  • gets rid of the deprecated methods that existed on BaseLanguageModel

very wip, I don't expect CI to pass

Copy link

vercel bot commented Aug 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
langchainjs-docs Ready Ready Preview Comment Aug 20, 2025 3:05am
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
langchainjs-api-refs Ignored Ignored Aug 20, 2025 3:05am

@hntrl hntrl force-pushed the hunter/v1-chat-model branch from dc230b5 to 8463f1b Compare August 20, 2025 02:36
* and the run ID.
*/
handleChatModelStart?(
llm: Serialized,
messages: BaseMessage[][],
messages: Message[],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the inner workings of BaseLanguageModel lets you parse multiple prompts at once, whereas the new chat model class only takes one string of messages

Comment on lines +74 to +78
| SimpleChatModel<Runnable<Message, Output>>
| SimpleChatModel<Runnable<Message, { raw: Message; parsed: Output }>> {
return super.withStructuredOutput(schema, config) as
| SimpleChatModel<Runnable<Message, Output>>
| SimpleChatModel<Runnable<Message, { raw: Message; parsed: Output }>>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately I'm not sure if there's a clean solution to avoid this verbose typing

Comment on lines +118 to +133
.bindTools([makeToolDefinition("tool1")])
.withStructuredOutput({
type: "object",
properties: {},
required: [],
})
.bindTools([makeToolDefinition("tool2")])
.withConfig({
tool_choice: "any",
})
.withStructuredOutput({
type: "object",
properties: {},
required: [],
})
.bindTools([makeToolDefinition("tool3")]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would expect that this composition means:

  • we keep tools 1-3
  • tool_choice will be any
  • we only use the second withStructuredOutput

Comment on lines +222 to +237
constructor(protected params: BaseChatModelParams) {
super(params);
this.verbose = params.verbose ?? false;
this.callbacks = params.callbacks;
this.tags = params.tags ?? [];
this.metadata = params.metadata ?? {};
this.cache = iife(() => {
if (typeof params.cache === "object") {
return params.cache;
} else if (params.cache) {
return InMemoryCacheV1.global();
}
return undefined;
});
this.caller = new AsyncCaller(params ?? {});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this constructor is largely just taking after BaseLangChain


export abstract class BaseChatModelV1<
CallOptions extends BaseChatModelCallOptions = BaseChatModelCallOptions,
TOutputParser extends ChatModelOutputParser | undefined = undefined
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TOutputParser actually determines whats on the output end of the runnable -- if we want structured output out we use whats defined on the output of the passed in parser, if its undefined we use Message

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has a naming conflict with non-chat model output parsers, so am open to ideas on what to name this if we stick w/ this

Comment on lines +350 to +357
if (this.outputParser === undefined) {
yield chunk as InferChatModelOutput<TOutputParser>;
} else {
yield this.outputParser.invoke(
chunk,
callOptions
) as InferChatModelOutput<TOutputParser>;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unclear on what the semantics should be for parsing chunks

  • run the output parser on each chunk (what this is currently doing)
  • only run the output parser on the aggregated chunk

Comment on lines +604 to +608
return instance.withOutputParser(
toolMessageParser.withConfig({
runName: "StructuredOutput",
})
);
Copy link
Contributor Author

@hntrl hntrl Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of returning a runnable, we return a reconstructed instance that has a different outputParser

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant