Skip to content

Commit b88f8bc

Browse files
committed
fix(google-common): set function calling mode to auto when responseMimeType is application/json
1 parent cacc137 commit b88f8bc

File tree

6 files changed

+144
-11
lines changed

6 files changed

+144
-11
lines changed

libs/providers/langchain-google-common/src/chat_models.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
GoogleSearchToolSetting,
4040
GoogleSpeechConfig,
4141
GeminiJsonSchema,
42+
GoogleAIResponseMimeType,
4243
} from "./types.js";
4344
import {
4445
convertToGeminiTools,
@@ -220,6 +221,8 @@ export abstract class ChatGoogleBase<AuthOptions>
220221

221222
responseModalities?: GoogleAIModelModality[];
222223

224+
responseMimeType?: GoogleAIResponseMimeType;
225+
223226
// May intentionally be undefined, meaning to compute this.
224227
convertSystemMessageToHumanContent: boolean | undefined;
225228

@@ -455,6 +458,10 @@ export abstract class ChatGoogleBase<AuthOptions>
455458
config?: StructuredOutputMethodOptions<true>
456459
): Runnable<BaseLanguageModelInput, { raw: BaseMessage; parsed: RunOutput }>;
457460

461+
/**
462+
* Creates a structured output version that enforces a specific schema.
463+
* Automatically sets responseMimeType to "application/json" for optimal function calling.
464+
*/
458465
withStructuredOutput<
459466
// eslint-disable-next-line @typescript-eslint/no-explicit-any
460467
RunOutput extends Record<string, any> = Record<string, any>
@@ -530,7 +537,13 @@ export abstract class ChatGoogleBase<AuthOptions>
530537
keyName: functionName,
531538
});
532539
}
533-
const llm = this.bindTools(tools).withConfig({ tool_choice: functionName });
540+
// Configure for structured output: set responseMimeType to "application/json"
541+
// This ensures the function calling mode is set to "auto" instead of "any"
542+
// for optimal performance when extracting structured data
543+
const llm = this.bindTools(tools).withConfig({
544+
tool_choice: functionName,
545+
responseMimeType: "application/json"
546+
});
534547

535548
if (!includeRaw) {
536549
return llm.pipe(outputParser).withConfig({

libs/providers/langchain-google-common/src/tests/chat_models.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,69 @@ describe("Mock ChatGoogle - Gemini", () => {
17211721
expect(func.parameters?.properties?.greeterName?.nullable).toEqual(true);
17221722
});
17231723

1724+
test("4. Functions withStructuredOutput - default behavior", async () => {
1725+
const record: Record<string, any> = {};
1726+
const projectId = mockId();
1727+
const authOptions: MockClientAuthInfo = {
1728+
record,
1729+
projectId,
1730+
resultFile: "chat-4-mock.json",
1731+
};
1732+
1733+
const schema = {
1734+
type: "object",
1735+
properties: {
1736+
name: { type: "string", description: "The person's name" },
1737+
age: { type: "number", description: "The person's age" },
1738+
},
1739+
required: ["name", "age"],
1740+
};
1741+
1742+
// No responseMimeType set - should default to undefined
1743+
const model = new ChatGoogle({ authOptions }).withStructuredOutput(schema);
1744+
await model.invoke("Extract info about John Doe, software engineer");
1745+
1746+
const toolConfig = record?.opts?.data?.toolConfig;
1747+
expect(toolConfig.functionCallingConfig.mode).toEqual("auto");
1748+
expect(toolConfig.functionCallingConfig.allowedFunctionNames).toEqual(["extract"]);
1749+
1750+
const generationConfig = record?.opts?.data?.generationConfig;
1751+
expect(generationConfig.responseMimeType).toEqual("application/json");
1752+
});
1753+
1754+
test("4. Functions withStructuredOutput - overrides user setting", async () => {
1755+
const record: Record<string, any> = {};
1756+
const projectId = mockId();
1757+
const authOptions: MockClientAuthInfo = {
1758+
record,
1759+
projectId,
1760+
resultFile: "chat-4-mock.json",
1761+
};
1762+
1763+
const schema = {
1764+
type: "object",
1765+
properties: {
1766+
sender: { type: "string", description: "Email sender" },
1767+
subject: { type: "string", description: "Email subject" },
1768+
},
1769+
required: ["sender", "subject"],
1770+
};
1771+
1772+
// User explicitly sets text/plain - withStructuredOutput should override it
1773+
const model = new ChatGoogle({
1774+
authOptions,
1775+
responseMimeType: "text/plain"
1776+
}).withStructuredOutput(schema);
1777+
await model.invoke("Analyze email from [email protected], subject: Project Update");
1778+
1779+
const toolConfig = record?.opts?.data?.toolConfig;
1780+
expect(toolConfig.functionCallingConfig.mode).toEqual("auto");
1781+
expect(toolConfig.functionCallingConfig.allowedFunctionNames).toEqual(["extract"]);
1782+
1783+
const generationConfig = record?.opts?.data?.generationConfig;
1784+
expect(generationConfig.responseMimeType).toEqual("application/json");
1785+
});
1786+
17241787
test("4. Functions - results", async () => {
17251788
const record: Record<string, any> = {};
17261789
const projectId = mockId();

libs/providers/langchain-google-common/src/utils/common.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,17 @@ export function copyAIModelParams(
3131
}
3232

3333
function processToolChoice(
34-
toolChoice: GoogleAIBaseLanguageModelCallOptions["tool_choice"],
35-
allowedFunctionNames: GoogleAIBaseLanguageModelCallOptions["allowed_function_names"]
34+
options: GoogleAIBaseLanguageModelCallOptions | undefined
3635
):
3736
| {
3837
tool_choice: "any" | "auto" | "none";
3938
allowed_function_names?: string[];
4039
}
4140
| undefined {
41+
const toolChoice = options?.tool_choice;
42+
const allowedFunctionNames = options?.allowed_function_names;
43+
const responseMimeType = options?.responseMimeType;
44+
4245
if (!toolChoice) {
4346
if (allowedFunctionNames) {
4447
// Allowed func names is passed, return 'any' so it forces the model to use a tool.
@@ -58,9 +61,11 @@ function processToolChoice(
5861
}
5962
if (typeof toolChoice === "string") {
6063
// String representing the function name.
61-
// Return any to force the model to predict the specified function call.
64+
// Use "auto" mode when responseMimeType is "application/json" for optimal performance
65+
const isJsonMode = responseMimeType === "application/json";
66+
const mode = isJsonMode ? "auto" : "any";
6267
return {
63-
tool_choice: "any",
68+
tool_choice: mode,
6469
allowed_function_names: [...(allowedFunctionNames ?? []), toolChoice],
6570
};
6671
}
@@ -205,10 +210,10 @@ export function copyAIModelParamsInto(
205210
options?.speechConfig ?? params?.speechConfig ?? target?.speechConfig
206211
);
207212
ret.streaming = options?.streaming ?? params?.streaming ?? target?.streaming;
208-
const toolChoice = processToolChoice(
209-
options?.tool_choice,
210-
options?.allowed_function_names
211-
);
213+
const toolChoice = processToolChoice({
214+
...options,
215+
responseMimeType: options?.responseMimeType ?? params?.responseMimeType ?? target?.responseMimeType
216+
});
212217
if (toolChoice) {
213218
ret.tool_choice = toolChoice.tool_choice;
214219
ret.allowed_function_names = toolChoice.allowed_function_names;

libs/providers/langchain-google-common/src/utils/gemini.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,10 +1833,12 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI {
18331833
};
18341834
}
18351835

1836-
// force tool choice to be a single function name in case of structured output
1836+
const isJsonMode = parameters.responseMimeType === "application/json";
1837+
const mode = isJsonMode ? "auto" : "any";
1838+
18371839
return {
18381840
functionCallingConfig: {
1839-
mode: "any",
1841+
mode,
18401842
allowedFunctionNames: [parameters.tool_choice],
18411843
},
18421844
};

libs/providers/langchain-google-genai/src/chat_models.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ export interface GoogleGenerativeAIChatInput
201201

202202
/**
203203
* Google Generative AI chat model integration.
204+
*
205+
* Supports JSON mode, structured output, function calling, and multimodal inputs.
204206
*
205207
* Setup:
206208
* Install `@langchain/google-genai` and set an environment variable named `GOOGLE_API_KEY`.
@@ -455,6 +457,29 @@ export interface GoogleGenerativeAIChatInput
455457
* <br />
456458
*
457459
* <details>
460+
* <summary><strong>JSON Mode</strong></summary>
461+
*
462+
* ```typescript
463+
* const jsonLlm = llm.withConfig({
464+
* responseMimeType: "application/json"
465+
* });
466+
* const jsonResponse = await jsonLlm.invoke(
467+
* "Return a JSON object with key 'randomInts' and a value of 10 random integers between 0-99"
468+
* );
469+
* console.log(jsonResponse.content);
470+
* ```
471+
*
472+
* ```txt
473+
* {
474+
* "randomInts": [23, 87, 45, 12, 78, 34, 56, 90, 11, 67]
475+
* }
476+
* ```
477+
* </details>
478+
*
479+
* <br />
480+
*
481+
*
482+
* <details>
458483
* <summary><strong>Multimodal</strong></summary>
459484
*
460485
* ```typescript

libs/providers/langchain-google-vertexai/src/chat_models.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export interface ChatVertexAIInput extends ChatGoogleInput {}
77

88
/**
99
* Integration with Google Vertex AI chat models.
10+
*
11+
* Supports JSON mode, structured output, function calling, and multimodal inputs.
1012
*
1113
* Setup:
1214
* Install `@langchain/google-vertexai` and set your stringified
@@ -249,6 +251,29 @@ export interface ChatVertexAIInput extends ChatGoogleInput {}
249251
* <br />
250252
*
251253
* <details>
254+
* <summary><strong>JSON Mode</strong></summary>
255+
*
256+
* ```typescript
257+
* const jsonLlm = llm.withConfig({
258+
* responseMimeType: "application/json"
259+
* });
260+
* const jsonResponse = await jsonLlm.invoke(
261+
* "Return a JSON object with key 'randomInts' and a value of 10 random integers between 0-99"
262+
* );
263+
* console.log(jsonResponse.content);
264+
* ```
265+
*
266+
* ```txt
267+
* {
268+
* "randomInts": [23, 87, 45, 12, 78, 34, 56, 90, 11, 67]
269+
* }
270+
* ```
271+
* </details>
272+
*
273+
* <br />
274+
*
275+
*
276+
* <details>
252277
* <summary><strong>Usage Metadata</strong></summary>
253278
*
254279
* ```typescript

0 commit comments

Comments
 (0)