Checked other resources
Example Code
import { ChatVertexAI } from "@langchain/google-vertexai";
const model = new ChatVertexAI({
model: "gemini-2.5-flash",
temperature: 0,
}).withConfig({
tools: [{ functionDeclarations: [myFunctionDeclaration] }],
// Intended: force the model to call this function (Gemini "ANY" mode).
tool_choice: myFunctionDeclaration.name,
});
const result = await model.invoke(messages);
// Expected: request carries toolConfig.functionCallingConfig.mode: "ANY",
// guaranteeing the function call with schema-constrained decoding.
// Actual: the request body contains mode: "any" (lowercase — inspect the
// outgoing payload; this is model-independent), which Vertex AI silently
// ignores, leaving default AUTO mode: the model may answer in plain text,
// and function-call args lose ANY-mode schema anchoring.
Error Message and Stack Trace (if applicable)
No response
Description
formatToolConfig in @langchain/google-common builds the request's
toolConfig.functionCallingConfig.mode using lowercase values
("auto" | "any" | "none"), both for the pass-through case and for the
forced-function-name case:
https://github.com/langchain-ai/langchainjs/blob/main/libs/providers/langchain-google-common/src/utils/gemini.ts#L1983-L2008
if (["auto", "any", "none"].includes(parameters.tool_choice)) {
config = {
functionCallingConfig: {
mode: parameters.tool_choice as "auto" | "any" | "none",
...
The Vertex AI REST API defines FunctionCallingConfig.mode as a protobuf
enum with values MODE_UNSPECIFIED | AUTO | ANY | NONE (uppercase). In our
testing against the production Vertex AI generateContent endpoint, a
lowercase mode is not rejected — the request returns 200 and behaves
exactly as if no functionCallingConfig had been sent (i.e. AUTO). That
makes the defect completely silent.
Two independent lines of evidence that the field is ignored (not just
mis-set):
- Request-payload inspection (model-independent, no behavioral test
needed): intercept/log the outgoing request body and the lowercase
toolConfig.functionCallingConfig.mode is visible for any model.
- The
NONE test (behavioral, verified with gemini-3.5-flash via the
global endpoint): with tool_choice: "none" — i.e. mode: "none" on the
wire — the model still emits function calls. That is categorically
impossible if the mode were honored, so the field is being ignored, and
requests run under default AUTO semantics.
The practical consequences follow from Google's documented mode semantics:
tool_choice: "<functionName>" / "any" does NOT force the call (the model
may answer in plain text instead), does NOT restrict to
allowedFunctionNames, and the response loses ANY-mode's guaranteed
schema-constrained function call. This is nasty to diagnose because the
model usually calls the tool anyway when prompted to — behavior degrades
intermittently rather than anything erroring.
By contrast, @langchain/google-genai converts the same inputs to the
uppercase enum via FunctionCallingMode.ANY in convertToolsToGenAI
(libs/providers/langchain-google-genai/src/utils/tools.ts), which works
correctly — so the two Google wrappers silently disagree about whether
tool_choice does anything.
Suggested fix
Map the user-facing lowercase values to the uppercase wire enum in
formatToolConfig, the same way formatGenerationConfig in the same file
already maps lowercase reasoningLevel values to uppercase thinkingLevel
enum values via its levelMap:
const modeMap = { auto: "AUTO", any: "ANY", none: "NONE" } as const;
(The GeminiRequest type's mode union would need the corresponding
update, or a cast at the assignment.)
Affected versions: observed in @langchain/google-common 2.1.31 and 2.2.0
(latest stable), 3.0.0-dev, and current main.
System Info
- @langchain/google-common 2.1.31 / 2.2.0 (also inspected 3.0.0-dev and main)
- @langchain/google-vertexai 2.1.31
- Node.js 22.x
- Behavioral verification (the NONE test above): Vertex AI
generateContent
with gemini-3.5-flash (global endpoint). The lowercase serialization
itself is model-independent (confirmed by request-payload inspection).
Checked other resources
Example Code
Error Message and Stack Trace (if applicable)
No response
Description
formatToolConfigin@langchain/google-commonbuilds the request'stoolConfig.functionCallingConfig.modeusing lowercase values(
"auto" | "any" | "none"), both for the pass-through case and for theforced-function-name case:
https://github.com/langchain-ai/langchainjs/blob/main/libs/providers/langchain-google-common/src/utils/gemini.ts#L1983-L2008
The Vertex AI REST API defines
FunctionCallingConfig.modeas a protobufenum with values
MODE_UNSPECIFIED | AUTO | ANY | NONE(uppercase). In ourtesting against the production Vertex AI
generateContentendpoint, alowercase
modeis not rejected — the request returns 200 and behavesexactly as if no
functionCallingConfighad been sent (i.e.AUTO). Thatmakes the defect completely silent.
Two independent lines of evidence that the field is ignored (not just
mis-set):
needed): intercept/log the outgoing request body and the lowercase
toolConfig.functionCallingConfig.modeis visible for any model.NONEtest (behavioral, verified withgemini-3.5-flashvia theglobal endpoint): with
tool_choice: "none"— i.e.mode: "none"on thewire — the model still emits function calls. That is categorically
impossible if the mode were honored, so the field is being ignored, and
requests run under default
AUTOsemantics.The practical consequences follow from Google's documented mode semantics:
tool_choice: "<functionName>"/"any"does NOT force the call (the modelmay answer in plain text instead), does NOT restrict to
allowedFunctionNames, and the response loses ANY-mode's guaranteedschema-constrained function call. This is nasty to diagnose because the
model usually calls the tool anyway when prompted to — behavior degrades
intermittently rather than anything erroring.
By contrast,
@langchain/google-genaiconverts the same inputs to theuppercase enum via
FunctionCallingMode.ANYinconvertToolsToGenAI(
libs/providers/langchain-google-genai/src/utils/tools.ts), which workscorrectly — so the two Google wrappers silently disagree about whether
tool_choicedoes anything.Suggested fix
Map the user-facing lowercase values to the uppercase wire enum in
formatToolConfig, the same wayformatGenerationConfigin the same filealready maps lowercase
reasoningLevelvalues to uppercasethinkingLevelenum values via its
levelMap:(The
GeminiRequesttype'smodeunion would need the correspondingupdate, or a cast at the assignment.)
Affected versions: observed in
@langchain/google-common2.1.31 and 2.2.0(latest stable), 3.0.0-dev, and current
main.System Info
generateContentwith
gemini-3.5-flash(global endpoint). The lowercase serializationitself is model-independent (confirmed by request-payload inspection).