Checked other resources
Example Code
/**
* Dev script for testing Gemini's built-in code execution tool.
* Invokes (or streams) the model with a prompt that requires computation and saves the
* raw AIMessage / AIMessageChunk JSON to timestamped files in this directory.
*
* Run via: bun run gemini-code-execution-test.ts
*/
import { type AIMessageChunk, HumanMessage, type MessageOutputVersion } from "@langchain/core/messages";
import { ChatGoogle } from "@langchain/google/node";
const MODEL = "gemini-2.5-flash";
const PROMPT = "Render the normal distribution of IQ scores into a graphic for me";
function buildLlm(outputVersion: MessageOutputVersion) {
return new ChatGoogle({
model: MODEL,
outputVersion,
}).bindTools([{ codeExecution: {} }]);
}
const llms: Record<MessageOutputVersion, ReturnType<typeof buildLlm>> = {
v0: buildLlm("v0"),
v1: buildLlm("v1"),
};
function makeBasePath(outputVersion: MessageOutputVersion, mode: "invoke" | "stream"): string {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
return `${import.meta.dir}/gemini-code-execution-${timestamp}-${outputVersion}-${mode}`;
}
async function runInvoke(outputVersion: MessageOutputVersion): Promise<void> {
const llm = llms[outputVersion];
console.log(`[invoke] outputVersion="${outputVersion}" — "${PROMPT}"`);
const result = await llm.invoke([new HumanMessage(PROMPT)]);
const outPath = `${makeBasePath(outputVersion, "invoke")}.json`;
await Bun.write(outPath, JSON.stringify(result.toJSON(), null, 2));
console.log(`Saved: ${outPath}`);
}
async function collectStream(outputVersion: MessageOutputVersion): Promise<AIMessageChunk[]> {
const llm = llms[outputVersion];
console.log(`[stream] outputVersion="${outputVersion}" — "${PROMPT}"`);
const chunks: AIMessageChunk[] = [];
let firstChunkAt: number | null = null;
const invokedAt = Date.now();
for await (const chunk of await llm.stream([new HumanMessage(PROMPT)])) {
firstChunkAt ??= Date.now();
chunks.push(chunk);
}
const now = Date.now();
const streamDurationMs = firstChunkAt !== null ? now - firstChunkAt : 0;
const totalDurationMs = now - invokedAt;
console.log(`Stream duration (first → last chunk): ${streamDurationMs}ms`);
console.log(`Total response time (invoke → last chunk): ${totalDurationMs}ms`);
return chunks;
}
async function saveChunks(chunks: AIMessageChunk[], basePath: string): Promise<void> {
const outPath = `${basePath}-chunks.json`;
await Bun.write(
outPath,
JSON.stringify(
chunks.map((c) => c.toJSON()),
null,
2,
),
);
console.log(`Saved: ${outPath}`);
}
async function saveCollected(chunks: AIMessageChunk[], basePath: string): Promise<void> {
const [first, ...rest] = chunks;
if (!first) throw new Error("No chunks received from stream");
const collected = rest.reduce((acc, chunk) => acc.concat(chunk), first);
const outPath = `${basePath}-collected.json`;
await Bun.write(outPath, JSON.stringify(collected.toJSON(), null, 2));
console.log(`Saved: ${outPath}`);
}
async function runStream(outputVersion: MessageOutputVersion): Promise<void> {
const chunks = await collectStream(outputVersion);
const basePath = makeBasePath(outputVersion, "stream");
await Promise.all([saveChunks(chunks, basePath), saveCollected(chunks, basePath)]);
}
async function runCodeExecution(outputVersion: MessageOutputVersion, stream = false): Promise<void> {
if (stream) {
await runStream(outputVersion);
} else {
await runInvoke(outputVersion);
}
}
await runCodeExecution("v0", true);
// await runCodeExecution("v1", true);
Error Message and Stack Trace (if applicable)
No response
Description
As you can see from the example output:
chunks is .stream chunks serialized as an array
collected is .stream chunks concatenated using chunk.concat(newChunk) then serialized using .toJSON()
In the first case (first pair or files):
The "collected" AIMessageChunk is correctly one text part. And it equals the originalTextContentBlock.
In the second case (second pair of files):
The "collected" AIMessageChunk collects text parts up to the first non-text part, then it just appends the rest as they were in the original chunks. The originalTextContentBlock is still correct.
I believe the original intention was for ALL consecutive text type content part blocks to be collapsed into one on .concat(), not just until the first non-text block.
gemini-code-execution-2026-03-31T13-29-50-785Z-v0-stream-chunks.json
gemini-code-execution-2026-03-31T13-29-50-785Z-v0-stream-collected.json
gemini-code-execution-2026-03-31T13-32-46-251Z-v0-stream-chunks.json
gemini-code-execution-2026-03-31T13-32-46-251Z-v0-stream-collected.json
System Info
Package: @langchain/google
Version: 0.1.9
Companion: @langchain/core 1.1.37 (peerDependency enforced)
Runtime: bun 1.3.11
Platform: Ubuntu 25.10 (Linux 6.17.0-14-generic)
Checked other resources
Example Code
Error Message and Stack Trace (if applicable)
No response
Description
As you can see from the example output:
chunksis.streamchunks serialized as an arraycollectedis.streamchunks concatenated usingchunk.concat(newChunk)then serialized using.toJSON()In the first case (first pair or files):
The "collected"
AIMessageChunkis correctly onetextpart. And it equals theoriginalTextContentBlock.In the second case (second pair of files):
The "collected"
AIMessageChunkcollectstextparts up to the first non-textpart, then it just appends the rest as they were in the original chunks. TheoriginalTextContentBlockis still correct.I believe the original intention was for ALL consecutive
texttype content part blocks to be collapsed into one on.concat(), not just until the first non-text block.gemini-code-execution-2026-03-31T13-29-50-785Z-v0-stream-chunks.json
gemini-code-execution-2026-03-31T13-29-50-785Z-v0-stream-collected.json
gemini-code-execution-2026-03-31T13-32-46-251Z-v0-stream-chunks.json
gemini-code-execution-2026-03-31T13-32-46-251Z-v0-stream-collected.json
System Info
Package:
@langchain/googleVersion:
0.1.9Companion:
@langchain/core1.1.37(peerDependency enforced)Runtime:
bun1.3.11Platform:
Ubuntu 25.10 (Linux 6.17.0-14-generic)